#
tokens: 47987/50000 42/192 files (page 2/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 4. Use http://codebase.md/stripe/agent-toolkit?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   └── workflows
│       ├── main.yml
│       ├── npm_agent_toolkit_release.yml
│       ├── npm_mcp_release.yml
│       └── pypi_release.yml
├── .gitignore
├── .vscode
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── evals
│   ├── .env.example
│   ├── .gitignore
│   ├── braintrust_openai.ts
│   ├── cases.ts
│   ├── eval.ts
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── README.md
│   ├── scorer.ts
│   └── tsconfig.json
├── gemini-extension.json
├── LICENSE
├── modelcontextprotocol
│   ├── .dxtignore
│   ├── .gitignore
│   ├── .node-version
│   ├── .prettierrc
│   ├── build-dxt.js
│   ├── Dockerfile
│   ├── eslint.config.mjs
│   ├── jest.config.ts
│   ├── manifest.json
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── README.md
│   ├── src
│   │   ├── index.ts
│   │   └── test
│   │       └── index.test.ts
│   ├── stripe_icon.png
│   └── tsconfig.json
├── python
│   ├── .editorconfig
│   ├── .flake8
│   ├── examples
│   │   ├── crewai
│   │   │   ├── .env.template
│   │   │   ├── main.py
│   │   │   └── README.md
│   │   ├── langchain
│   │   │   ├── __init__.py
│   │   │   ├── .env.template
│   │   │   ├── main.py
│   │   │   └── README.md
│   │   ├── openai
│   │   │   ├── .env.template
│   │   │   ├── customer_support
│   │   │   │   ├── .env.template
│   │   │   │   ├── emailer.py
│   │   │   │   ├── env.py
│   │   │   │   ├── main.py
│   │   │   │   ├── pyproject.toml
│   │   │   │   ├── README.md
│   │   │   │   ├── repl.py
│   │   │   │   └── support_agent.py
│   │   │   ├── file_search
│   │   │   │   ├── main.py
│   │   │   │   └── README.md
│   │   │   └── web_search
│   │   │       ├── .env.template
│   │   │       ├── main.py
│   │   │       └── README.md
│   │   └── strands
│   │       └── main.py
│   ├── Makefile
│   ├── pyproject.toml
│   ├── README.md
│   ├── requirements.txt
│   ├── stripe_agent_toolkit
│   │   ├── __init__.py
│   │   ├── api.py
│   │   ├── configuration.py
│   │   ├── crewai
│   │   │   ├── tool.py
│   │   │   └── toolkit.py
│   │   ├── functions.py
│   │   ├── langchain
│   │   │   ├── tool.py
│   │   │   └── toolkit.py
│   │   ├── openai
│   │   │   ├── hooks.py
│   │   │   ├── tool.py
│   │   │   └── toolkit.py
│   │   ├── prompts.py
│   │   ├── schema.py
│   │   ├── strands
│   │   │   ├── __init__.py
│   │   │   ├── hooks.py
│   │   │   ├── tool.py
│   │   │   └── toolkit.py
│   │   └── tools.py
│   └── tests
│       ├── __init__.py
│       ├── test_configuration.py
│       └── test_functions.py
├── README.md
├── SECURITY.md
└── typescript
    ├── .gitignore
    ├── .prettierrc
    ├── eslint.config.mjs
    ├── examples
    │   ├── ai-sdk
    │   │   ├── .env.template
    │   │   ├── index.ts
    │   │   ├── package.json
    │   │   ├── README.md
    │   │   └── tsconfig.json
    │   ├── cloudflare
    │   │   ├── .dev.vars.example
    │   │   ├── .gitignore
    │   │   ├── biome.json
    │   │   ├── package.json
    │   │   ├── README.md
    │   │   ├── src
    │   │   │   ├── app.ts
    │   │   │   ├── imageGenerator.ts
    │   │   │   ├── index.ts
    │   │   │   ├── oauth.ts
    │   │   │   └── utils.ts
    │   │   ├── tsconfig.json
    │   │   ├── worker-configuration.d.ts
    │   │   └── wrangler.jsonc
    │   ├── langchain
    │   │   ├── .env.template
    │   │   ├── index.ts
    │   │   ├── package.json
    │   │   ├── README.md
    │   │   └── tsconfig.json
    │   └── openai
    │       ├── .env.template
    │       ├── index.ts
    │       ├── package.json
    │       ├── README.md
    │       └── tsconfig.json
    ├── jest.config.ts
    ├── package.json
    ├── pnpm-lock.yaml
    ├── pnpm-workspace.yaml
    ├── README.md
    ├── src
    │   ├── ai-sdk
    │   │   ├── index.ts
    │   │   ├── tool.ts
    │   │   └── toolkit.ts
    │   ├── cloudflare
    │   │   ├── index.ts
    │   │   └── README.md
    │   ├── langchain
    │   │   ├── index.ts
    │   │   ├── tool.ts
    │   │   └── toolkit.ts
    │   ├── modelcontextprotocol
    │   │   ├── index.ts
    │   │   ├── README.md
    │   │   ├── register-paid-tool.ts
    │   │   └── toolkit.ts
    │   ├── openai
    │   │   ├── index.ts
    │   │   └── toolkit.ts
    │   ├── shared
    │   │   ├── api.ts
    │   │   ├── balance
    │   │   │   └── retrieveBalance.ts
    │   │   ├── configuration.ts
    │   │   ├── coupons
    │   │   │   ├── createCoupon.ts
    │   │   │   └── listCoupons.ts
    │   │   ├── customers
    │   │   │   ├── createCustomer.ts
    │   │   │   └── listCustomers.ts
    │   │   ├── disputes
    │   │   │   ├── listDisputes.ts
    │   │   │   └── updateDispute.ts
    │   │   ├── documentation
    │   │   │   └── searchDocumentation.ts
    │   │   ├── invoiceItems
    │   │   │   └── createInvoiceItem.ts
    │   │   ├── invoices
    │   │   │   ├── createInvoice.ts
    │   │   │   ├── finalizeInvoice.ts
    │   │   │   └── listInvoices.ts
    │   │   ├── paymentIntents
    │   │   │   └── listPaymentIntents.ts
    │   │   ├── paymentLinks
    │   │   │   └── createPaymentLink.ts
    │   │   ├── prices
    │   │   │   ├── createPrice.ts
    │   │   │   └── listPrices.ts
    │   │   ├── products
    │   │   │   ├── createProduct.ts
    │   │   │   └── listProducts.ts
    │   │   ├── refunds
    │   │   │   └── createRefund.ts
    │   │   ├── subscriptions
    │   │   │   ├── cancelSubscription.ts
    │   │   │   ├── listSubscriptions.ts
    │   │   │   └── updateSubscription.ts
    │   │   └── tools.ts
    │   └── test
    │       ├── modelcontextprotocol
    │       │   └── register-paid-tool.test.ts
    │       └── shared
    │           ├── balance
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           ├── configuration.test.ts
    │           ├── customers
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           ├── disputes
    │           │   └── functions.test.ts
    │           ├── documentation
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           ├── invoiceItems
    │           │   ├── functions.test.ts
    │           │   ├── parameters.test.ts
    │           │   └── prompts.test.ts
    │           ├── invoices
    │           │   ├── functions.test.ts
    │           │   ├── parameters.test.ts
    │           │   └── prompts.test.ts
    │           ├── paymentIntents
    │           │   ├── functions.test.ts
    │           │   ├── parameters.test.ts
    │           │   └── prompts.test.ts
    │           ├── paymentLinks
    │           │   ├── functions.test.ts
    │           │   ├── parameters.test.ts
    │           │   └── prompts.test.ts
    │           ├── prices
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           ├── products
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           ├── refunds
    │           │   ├── functions.test.ts
    │           │   └── parameters.test.ts
    │           └── subscriptions
    │               ├── functions.test.ts
    │               ├── parameters.test.ts
    │               └── prompts.test.ts
    ├── tsconfig.json
    └── tsup.config.ts
```

# Files

--------------------------------------------------------------------------------
/typescript/src/test/shared/subscriptions/prompts.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {listSubscriptionsPrompt} from '@/shared/subscriptions/listSubscriptions';
 2 | import {cancelSubscriptionPrompt} from '@/shared/subscriptions/cancelSubscription';
 3 | import {updateSubscriptionPrompt} from '@/shared/subscriptions/updateSubscription';
 4 | 
 5 | describe('listSubscriptionsPrompt', () => {
 6 |   it('should return the correct prompt with no context', () => {
 7 |     const prompt = listSubscriptionsPrompt({});
 8 | 
 9 |     expect(prompt).toContain('This tool will list all subscriptions in Stripe');
10 |     expect(prompt).toContain('four arguments');
11 |     expect(prompt).toContain('- customer (str, optional)');
12 |     expect(prompt).toContain('- price (str, optional)');
13 |     expect(prompt).toContain('- status (str, optional)');
14 |     expect(prompt).toContain('- limit (int, optional)');
15 |   });
16 | 
17 |   it('should return the correct prompt with customer in context', () => {
18 |     const prompt = listSubscriptionsPrompt({customer: 'cus_123'});
19 | 
20 |     expect(prompt).toContain('This tool will list all subscriptions in Stripe');
21 |     expect(prompt).toContain('three arguments');
22 |     expect(prompt).not.toContain('- customer (str, optional)');
23 |     expect(prompt).toContain('- price (str, optional)');
24 |     expect(prompt).toContain('- status (str, optional)');
25 |     expect(prompt).toContain('- limit (int, optional)');
26 |   });
27 | });
28 | 
29 | describe('cancelSubscriptionPrompt', () => {
30 |   it('should return the correct prompt', () => {
31 |     const prompt = cancelSubscriptionPrompt({});
32 | 
33 |     expect(prompt).toContain('This tool will cancel a subscription in Stripe');
34 |     expect(prompt).toContain('- subscription (str, required)');
35 |   });
36 | });
37 | 
38 | describe('updateSubscriptionPrompt', () => {
39 |   it('should return the correct prompt', () => {
40 |     const prompt = updateSubscriptionPrompt({});
41 | 
42 |     expect(prompt).toContain(
43 |       'This tool will update an existing subscription in Stripe'
44 |     );
45 |     expect(prompt).toContain('- subscription (str, required)');
46 |     expect(prompt).toContain('- proration_behavior (str, optional)');
47 |     expect(prompt).toContain('- items (array, optional)');
48 |   });
49 | });
50 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Bug report
 2 | description: Create a report to help us improve
 3 | labels: ["bug"]
 4 | body:
 5 |   - type: markdown
 6 |     attributes:
 7 |       value: |
 8 |         Thanks for taking the time to fill out this bug report!
 9 |   - type: textarea
10 |     id: what-happened
11 |     attributes:
12 |       label: Describe the bug
13 |       description: A clear and concise description of what the bug is.
14 |       placeholder: Tell us what you see!
15 |     validations:
16 |       required: true
17 |   - type: textarea
18 |     id: repro-steps
19 |     attributes:
20 |       label: To Reproduce
21 |       description: Steps to reproduce the behavior
22 |       placeholder: |
23 |         1. Fetch a '...'
24 |         2. Update the '....'
25 |         3. See error
26 |     validations:
27 |       required: true
28 |   - type: textarea
29 |     id: expected-behavior
30 |     attributes:
31 |       label: Expected behavior
32 |       description: A clear and concise description of what you expected to happen.
33 |     validations:
34 |       required: true
35 |   - type: textarea
36 |     id: code-snippets
37 |     attributes:
38 |       label: Code snippets
39 |       description: If applicable, add code snippets to help explain your problem.
40 |       render: Python
41 |     validations:
42 |       required: false
43 |   - type: input
44 |     id: os
45 |     attributes:
46 |       label: OS
47 |       placeholder: macOS
48 |     validations:
49 |       required: true
50 |   - type: input
51 |     id: language-version
52 |     attributes:
53 |       label: Language version
54 |       placeholder: Python 3.10.4
55 |     validations:
56 |       required: true
57 |   - type: input
58 |     id: lib-version
59 |     attributes:
60 |       label: Library version
61 |       placeholder: stripe-python v2.73.0
62 |     validations:
63 |       required: true
64 |   - type: input
65 |     id: api-version
66 |     attributes:
67 |       label: API version
68 |       description: See [Versioning](https://stripe.com/docs/api/versioning) in the API Reference to find which version you're using
69 |       placeholder: "2020-08-27"
70 |     validations:
71 |       required: true
72 |   - type: textarea
73 |     id: additional-context
74 |     attributes:
75 |       label: Additional context
76 |       description: Add any other context about the problem here.
77 |     validations:
78 |       required: false
79 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/disputes/listDisputes.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const listDisputesPrompt = (_context: Context = {}) => `
 7 | This tool will fetch a list of disputes in Stripe.
 8 | 
 9 | It takes the following arguments:
10 | - charge (string, optional): Only return disputes associated to the charge specified by this charge ID.
11 | - payment_intent (string, optional): Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.
12 | `;
13 | 
14 | export const listDisputesParameters = (_context: Context = {}) =>
15 |   z.object({
16 |     charge: z
17 |       .string()
18 |       .optional()
19 |       .describe(
20 |         'Only return disputes associated to the charge specified by this charge ID.'
21 |       ),
22 |     payment_intent: z
23 |       .string()
24 |       .optional()
25 |       .describe(
26 |         'Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.'
27 |       ),
28 |     limit: z
29 |       .number()
30 |       .int()
31 |       .min(1)
32 |       .max(100)
33 |       .default(10)
34 |       .optional()
35 |       .describe(
36 |         'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
37 |       ),
38 |   });
39 | 
40 | export const listDisputesAnnotations = () => ({
41 |   destructiveHint: false,
42 |   idempotentHint: true,
43 |   openWorldHint: true,
44 |   readOnlyHint: true,
45 |   title: 'List disputes',
46 | });
47 | 
48 | export const listDisputes = async (
49 |   stripe: Stripe,
50 |   context: Context,
51 |   params: z.infer<ReturnType<typeof listDisputesParameters>>
52 | ) => {
53 |   try {
54 |     const disputes = await stripe.disputes.list(
55 |       params,
56 |       context.account ? {stripeAccount: context.account} : undefined
57 |     );
58 | 
59 |     return disputes.data.map((dispute) => ({id: dispute.id}));
60 |   } catch (error) {
61 |     return 'Failed to list disputes';
62 |   }
63 | };
64 | 
65 | const tool = (context: Context): Tool => ({
66 |   method: 'list_disputes',
67 |   name: 'List Disputes',
68 |   description: listDisputesPrompt(context),
69 |   parameters: listDisputesParameters(context),
70 |   annotations: listDisputesAnnotations(),
71 |   actions: {
72 |     disputes: {
73 |       read: true,
74 |     },
75 |   },
76 |   execute: listDisputes,
77 | });
78 | 
79 | export default tool;
80 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/invoiceItems/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {createInvoiceItem} from '@/shared/invoiceItems/createInvoiceItem';
 2 | 
 3 | const Stripe = jest.fn().mockImplementation(() => ({
 4 |   invoiceItems: {
 5 |     create: jest.fn(),
 6 |   },
 7 | }));
 8 | 
 9 | let stripe: ReturnType<typeof Stripe>;
10 | 
11 | beforeEach(() => {
12 |   stripe = new Stripe('fake-api-key');
13 | });
14 | 
15 | describe('createInvoiceItem', () => {
16 |   it('should create an invoice item and return it', async () => {
17 |     const params = {
18 |       customer: 'cus_123456',
19 |       price: 'price_123456',
20 |       invoice: 'in_123456',
21 |     };
22 | 
23 |     const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
24 | 
25 |     const context = {};
26 | 
27 |     stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
28 | 
29 |     const result = await createInvoiceItem(stripe, context, params);
30 | 
31 |     expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, undefined);
32 |     expect(result).toEqual(mockInvoiceItem);
33 |   });
34 | 
35 |   it('should specify the connected account if included in context', async () => {
36 |     const params = {
37 |       customer: 'cus_123456',
38 |       price: 'price_123456',
39 |       invoice: 'in_123456',
40 |     };
41 | 
42 |     const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
43 | 
44 |     const context = {
45 |       account: 'acct_123456',
46 |     };
47 | 
48 |     stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
49 | 
50 |     const result = await createInvoiceItem(stripe, context, params);
51 | 
52 |     expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, {
53 |       stripeAccount: context.account,
54 |     });
55 |     expect(result).toEqual(mockInvoiceItem);
56 |   });
57 | 
58 |   it('should create an invoice item with a customer if included in context', async () => {
59 |     const params = {
60 |       price: 'price_123456',
61 |       invoice: 'in_123456',
62 |     };
63 | 
64 |     const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};
65 | 
66 |     const context = {
67 |       customer: 'cus_123456',
68 |     };
69 | 
70 |     stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);
71 | 
72 |     const result = await createInvoiceItem(stripe, context, params);
73 | 
74 |     expect(stripe.invoiceItems.create).toHaveBeenCalledWith(
75 |       {
76 |         ...params,
77 |         customer: context.customer,
78 |       },
79 |       undefined
80 |     );
81 |     expect(result).toEqual(mockInvoiceItem);
82 |   });
83 | });
84 | 
```

--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: CI
  2 | 
  3 | on:
  4 |   workflow_dispatch: {}
  5 |   push:
  6 |     branches:
  7 |       - main
  8 |   pull_request:
  9 |     branches:
 10 |       - main
 11 | 
 12 | jobs:
 13 |   typescript-build:
 14 |     name: Build - TypeScript
 15 |     runs-on: ubuntu-latest
 16 | 
 17 |     defaults:
 18 |       run:
 19 |         working-directory: ./typescript
 20 | 
 21 |     steps:
 22 |       - name: Checkout
 23 |         uses: actions/checkout@v3
 24 | 
 25 |       - name: pnpm
 26 |         uses: pnpm/action-setup@v4
 27 |         with:
 28 |           version: 9.11.0
 29 | 
 30 |       - name: Node
 31 |         uses: actions/setup-node@v4
 32 |         with:
 33 |           node-version: "18"
 34 | 
 35 |       - name: Install
 36 |         run: pnpm install --frozen-lockfile
 37 | 
 38 |       - name: Build
 39 |         run: pnpm run build
 40 | 
 41 |       - name: Clean
 42 |         run: pnpm run clean
 43 | 
 44 |       - name: Lint
 45 |         run: pnpm run lint
 46 | 
 47 |       - name: Prettier
 48 |         run: pnpm run prettier-check
 49 | 
 50 |       - name: Test
 51 |         run: pnpm run test
 52 | 
 53 |   modelcontextprotocol-build:
 54 |     name: Build - Model Context Protocol
 55 |     runs-on: ubuntu-latest
 56 | 
 57 |     defaults:
 58 |       run:
 59 |         working-directory: ./modelcontextprotocol
 60 | 
 61 |     steps:
 62 |       - name: Checkout
 63 |         uses: actions/checkout@v3
 64 | 
 65 |       - name: pnpm
 66 |         uses: pnpm/action-setup@v4
 67 |         with:
 68 |           version: 9.11.0
 69 | 
 70 |       - name: Node
 71 |         uses: actions/setup-node@v4
 72 |         with:
 73 |           node-version: "18"
 74 | 
 75 |       - name: Install
 76 |         run: pnpm install --frozen-lockfile
 77 | 
 78 |       - name: Build
 79 |         run: pnpm run build
 80 | 
 81 |       - name: Clean
 82 |         run: pnpm run clean
 83 | 
 84 |       - name: Lint
 85 |         run: pnpm run lint
 86 | 
 87 |       - name: Prettier
 88 |         run: pnpm run prettier-check
 89 | 
 90 |       - name: Test
 91 |         run: pnpm run test
 92 | 
 93 |   python-build:
 94 |     name: Build - Python
 95 |     runs-on: ubuntu-latest
 96 | 
 97 |     defaults:
 98 |       run:
 99 |         working-directory: ./python
100 | 
101 |     steps:
102 |       - name: Checkout
103 |         uses: actions/checkout@v3
104 | 
105 |       - name: Python
106 |         uses: actions/setup-python@v4
107 |         with:
108 |           python-version: "3.11"
109 | 
110 |       - name: Install
111 |         run: make venv
112 | 
113 |       - name: Build
114 |         run: |
115 |           set -x
116 |           source venv/bin/activate
117 |           rm -rf build dist *.egg-info
118 |           make build
119 |           python -m twine check dist/*
120 | 
121 |       - name: Test
122 |         run: |
123 |           make venv
124 |           make test
125 | 
```

--------------------------------------------------------------------------------
/python/examples/openai/file_search/main.py:
--------------------------------------------------------------------------------

```python
 1 | import asyncio
 2 | import os
 3 | from pydantic import BaseModel, Field
 4 | 
 5 | from dotenv import load_dotenv
 6 | load_dotenv()
 7 | 
 8 | from agents import Agent, Runner
 9 | from agents.tool import FileSearchTool
10 | 
11 | from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit
12 | 
13 | stripe_agent_toolkit = StripeAgentToolkit(
14 |     secret_key=os.getenv("STRIPE_SECRET_KEY"),
15 |     configuration={
16 |         "actions": {
17 |             "customers": {
18 |                 "create": True,
19 |             },
20 |             "products": {
21 |                 "create": True,
22 |             },
23 |             "prices": {
24 |                 "create": True,
25 |             },
26 |             "invoice_items": {
27 |                 "create": True,
28 |             },
29 |             "invoices": {
30 |                 "create": True,
31 |                 "update": True,
32 |             },
33 |         }
34 |     },
35 | )
36 | 
37 | class InvoiceOutput(BaseModel):
38 |     name: str = Field(description="The name of the customer")
39 |     email: str = Field(description="The email of the customer")
40 |     service: str = Field(description="The service that the customer is invoiced for")
41 |     amount_due: int = Field(description="The dollar amount due for the invoice. Convert text to dollar amounts if needed.")
42 |     id: str = Field(description="The id of the stripe invoice")
43 | 
44 | class InvoiceListOutput(BaseModel):
45 |     invoices: list[InvoiceOutput]
46 | 
47 | invoice_agent = Agent(
48 |     name="Invoice Agent",
49 |     instructions="You are an expert at using the Stripe API to create, finalize, and send invoices to customers.",
50 |     tools=stripe_agent_toolkit.get_tools(),
51 | )
52 | 
53 | file_search_agent = Agent(
54 |     name="File Search Agent",
55 |     instructions="You are an expert at searching for financial documents.",
56 |     tools=[
57 |         FileSearchTool(
58 |             max_num_results=50,
59 |             vector_store_ids=[os.getenv("OPENAI_VECTOR_STORE_ID")],
60 |         )
61 |     ],
62 |     output_type=InvoiceListOutput,
63 |     handoffs=[invoice_agent]
64 | )
65 | 
66 | async def main():
67 |     assignment = "Search for all customers that haven't paid across all of my documents. Handoff to the invoice agent to create, finalize, and send an invoice for each."
68 | 
69 |     outstanding_invoices = await Runner.run(
70 |         file_search_agent,
71 |         assignment,
72 |     )
73 | 
74 |     invoices = outstanding_invoices.final_output
75 | 
76 |     print(invoices)
77 | 
78 | if __name__ == "__main__":
79 |     asyncio.run(main())
80 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/paymentIntents/listPaymentIntents.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const listPaymentIntentsPrompt = (context: Context = {}) => {
 7 |   const customerArg = context.customer
 8 |     ? `The customer is already set in the context: ${context.customer}.`
 9 |     : `- customer (str, optional): The ID of the customer to list payment intents for.\n`;
10 | 
11 |   return `
12 | This tool will list payment intents in Stripe.
13 | 
14 | It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
15 | ${customerArg}
16 | - limit (int, optional): The number of payment intents to return.
17 | `;
18 | };
19 | 
20 | export const listPaymentIntents = async (
21 |   stripe: Stripe,
22 |   context: Context,
23 |   params: z.infer<ReturnType<typeof listPaymentIntentsParameters>>
24 | ) => {
25 |   try {
26 |     if (context.customer) {
27 |       params.customer = context.customer;
28 |     }
29 | 
30 |     const paymentIntents = await stripe.paymentIntents.list(
31 |       params,
32 |       context.account ? {stripeAccount: context.account} : undefined
33 |     );
34 | 
35 |     return paymentIntents.data;
36 |   } catch (error) {
37 |     return 'Failed to list payment intents';
38 |   }
39 | };
40 | 
41 | export const listPaymentIntentsAnnotations = () => ({
42 |   destructiveHint: false,
43 |   idempotentHint: true,
44 |   openWorldHint: true,
45 |   readOnlyHint: true,
46 |   title: 'List payment intents',
47 | });
48 | 
49 | export const listPaymentIntentsParameters = (
50 |   context: Context = {}
51 | ): z.AnyZodObject => {
52 |   const schema = z.object({
53 |     customer: z
54 |       .string()
55 |       .optional()
56 |       .describe('The ID of the customer to list payment intents for.'),
57 |     limit: z
58 |       .number()
59 |       .int()
60 |       .min(1)
61 |       .max(100)
62 |       .optional()
63 |       .describe(
64 |         'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
65 |       ),
66 |   });
67 | 
68 |   if (context.customer) {
69 |     return schema.omit({customer: true});
70 |   } else {
71 |     return schema;
72 |   }
73 | };
74 | 
75 | const tool = (context: Context): Tool => ({
76 |   method: 'list_payment_intents',
77 |   name: 'List Payment Intents',
78 |   description: listPaymentIntentsPrompt(context),
79 |   parameters: listPaymentIntentsParameters(context),
80 |   annotations: listPaymentIntentsAnnotations(),
81 |   actions: {
82 |     paymentIntents: {
83 |       read: true,
84 |     },
85 |   },
86 |   execute: listPaymentIntents,
87 | });
88 | 
89 | export default tool;
90 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/paymentLinks/createPaymentLink.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const createPaymentLinkPrompt = (_context: Context = {}) => `
 7 | This tool will create a payment link in Stripe.
 8 | 
 9 | It takes two arguments:
10 | - price (str): The ID of the price to create the payment link for.
11 | - quantity (int): The quantity of the product to include in the payment link.
12 | - redirect_url (str, optional): The URL to redirect to after the payment is completed.
13 | `;
14 | 
15 | export const createPaymentLink = async (
16 |   stripe: Stripe,
17 |   context: Context,
18 |   params: z.infer<ReturnType<typeof createPaymentLinkParameters>>
19 | ) => {
20 |   try {
21 |     const paymentLink = await stripe.paymentLinks.create(
22 |       {
23 |         line_items: [{price: params.price, quantity: params.quantity}],
24 |         ...(params.redirect_url
25 |           ? {
26 |               after_completion: {
27 |                 type: 'redirect',
28 |                 redirect: {
29 |                   url: params.redirect_url,
30 |                 },
31 |               },
32 |             }
33 |           : undefined),
34 |       },
35 |       context.account ? {stripeAccount: context.account} : undefined
36 |     );
37 | 
38 |     return {id: paymentLink.id, url: paymentLink.url};
39 |   } catch (error) {
40 |     return 'Failed to create payment link';
41 |   }
42 | };
43 | 
44 | export const createPaymentLinkAnnotations = () => ({
45 |   destructiveHint: false,
46 |   idempotentHint: false,
47 |   openWorldHint: true,
48 |   readOnlyHint: false,
49 |   title: 'Create payment link',
50 | });
51 | 
52 | export const createPaymentLinkParameters = (_context: Context = {}) =>
53 |   z.object({
54 |     price: z
55 |       .string()
56 |       .describe('The ID of the price to create the payment link for.'),
57 |     quantity: z
58 |       .number()
59 |       .int()
60 |       .describe('The quantity of the product to include.'),
61 |     redirect_url: z
62 |       .string()
63 |       .optional()
64 |       .describe('The URL to redirect to after the payment is completed.'),
65 |   });
66 | 
67 | const tool = (context: Context): Tool => ({
68 |   method: 'create_payment_link',
69 |   name: 'Create Payment Link',
70 |   description: createPaymentLinkPrompt(context),
71 |   parameters: createPaymentLinkParameters(context),
72 |   annotations: createPaymentLinkAnnotations(),
73 |   actions: {
74 |     paymentLinks: {
75 |       create: true,
76 |     },
77 |   },
78 |   execute: createPaymentLink,
79 | });
80 | 
81 | export default tool;
82 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/invoices/createInvoice.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const createInvoicePrompt = (context: Context = {}) => {
 7 |   const customerArg = context.customer
 8 |     ? `The customer is already set in the context: ${context.customer}.`
 9 |     : `- customer (str): The ID of the customer to create the invoice for.\n`;
10 | 
11 |   return `
12 |   This tool will create an invoice in Stripe.
13 |   
14 |   It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
15 |   ${customerArg}
16 |   - days_until_due (int, optional): The number of days until the invoice is due.
17 |   `;
18 | };
19 | 
20 | export const createInvoiceParameters = (
21 |   context: Context = {}
22 | ): z.AnyZodObject => {
23 |   const schema = z.object({
24 |     customer: z
25 |       .string()
26 |       .describe('The ID of the customer to create the invoice for.'),
27 |     days_until_due: z
28 |       .number()
29 |       .int()
30 |       .optional()
31 |       .describe('The number of days until the invoice is due.'),
32 |   });
33 | 
34 |   if (context.customer) {
35 |     return schema.omit({customer: true});
36 |   } else {
37 |     return schema;
38 |   }
39 | };
40 | 
41 | export const createInvoiceAnnotations = () => ({
42 |   destructiveHint: false,
43 |   idempotentHint: false,
44 |   openWorldHint: true,
45 |   readOnlyHint: false,
46 |   title: 'Create invoice',
47 | });
48 | 
49 | export const createInvoice = async (
50 |   stripe: Stripe,
51 |   context: Context,
52 |   params: z.infer<ReturnType<typeof createInvoiceParameters>>
53 | ) => {
54 |   try {
55 |     if (context.customer) {
56 |       params.customer = context.customer;
57 |     }
58 | 
59 |     const invoice = await stripe.invoices.create(
60 |       {
61 |         ...params,
62 |         collection_method: 'send_invoice',
63 |       },
64 |       context.account ? {stripeAccount: context.account} : undefined
65 |     );
66 | 
67 |     return {
68 |       id: invoice.id,
69 |       url: invoice.hosted_invoice_url,
70 |       customer: invoice.customer,
71 |       status: invoice.status,
72 |     };
73 |   } catch (error) {
74 |     return 'Failed to create invoice';
75 |   }
76 | };
77 | 
78 | const tool = (context: Context): Tool => ({
79 |   method: 'create_invoice',
80 |   name: 'Create Invoice',
81 |   description: createInvoicePrompt(context),
82 |   parameters: createInvoiceParameters(context),
83 |   annotations: createInvoiceAnnotations(),
84 |   actions: {
85 |     invoices: {
86 |       create: true,
87 |     },
88 |   },
89 |   execute: createInvoice,
90 | });
91 | 
92 | export default tool;
93 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/invoiceItems/createInvoiceItem.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const createInvoiceItem = async (
 7 |   stripe: Stripe,
 8 |   context: Context,
 9 |   params: z.infer<ReturnType<typeof createInvoiceItemParameters>>
10 | ) => {
11 |   try {
12 |     if (context.customer) {
13 |       params.customer = context.customer;
14 |     }
15 | 
16 |     const invoiceItem = await stripe.invoiceItems.create(
17 |       // @ts-ignore
18 |       params,
19 |       context.account ? {stripeAccount: context.account} : undefined
20 |     );
21 | 
22 |     return {
23 |       id: invoiceItem.id,
24 |       invoice: invoiceItem.invoice,
25 |     };
26 |   } catch (error) {
27 |     return 'Failed to create invoice item';
28 |   }
29 | };
30 | 
31 | export const createInvoiceItemAnnotations = () => ({
32 |   destructiveHint: false,
33 |   idempotentHint: false,
34 |   openWorldHint: true,
35 |   readOnlyHint: false,
36 |   title: 'Create invoice item',
37 | });
38 | 
39 | export const createInvoiceItemParameters = (
40 |   context: Context = {}
41 | ): z.AnyZodObject => {
42 |   const schema = z.object({
43 |     customer: z
44 |       .string()
45 |       .describe('The ID of the customer to create the invoice item for.'),
46 |     price: z.string().describe('The ID of the price for the item.'),
47 |     invoice: z
48 |       .string()
49 |       .describe('The ID of the invoice to create the item for.'),
50 |   });
51 | 
52 |   if (context.customer) {
53 |     return schema.omit({customer: true});
54 |   } else {
55 |     return schema;
56 |   }
57 | };
58 | 
59 | export const createInvoiceItemPrompt = (context: Context = {}) => {
60 |   const customerArg = context.customer
61 |     ? `The customer is already set in the context: ${context.customer}.`
62 |     : `- customer (str): The ID of the customer to create the invoice item for.\n`;
63 | 
64 |   return `
65 | This tool will create an invoice item in Stripe.
66 | 
67 | It takes ${context.customer ? 'two' : 'three'} arguments'}:
68 | ${customerArg}
69 | - price (str): The ID of the price to create the invoice item for.
70 | - invoice (str): The ID of the invoice to create the invoice item for.
71 | `;
72 | };
73 | 
74 | const tool = (context: Context): Tool => ({
75 |   method: 'create_invoice_item',
76 |   name: 'Create Invoice Item',
77 |   description: createInvoiceItemPrompt(context),
78 |   parameters: createInvoiceItemParameters(context),
79 |   annotations: createInvoiceItemAnnotations(),
80 |   actions: {
81 |     invoiceItems: {
82 |       create: true,
83 |     },
84 |   },
85 |   execute: createInvoiceItem,
86 | });
87 | 
88 | export default tool;
89 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/invoices/listInvoices.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | 
 6 | export const listInvoices = async (
 7 |   stripe: Stripe,
 8 |   context: Context,
 9 |   params: z.infer<ReturnType<typeof listInvoicesParameters>>
10 | ) => {
11 |   try {
12 |     if (context.customer) {
13 |       params.customer = context.customer;
14 |     }
15 | 
16 |     const invoices = await stripe.invoices.list(
17 |       params,
18 |       context.account ? {stripeAccount: context.account} : undefined
19 |     );
20 | 
21 |     return invoices.data;
22 |   } catch (error) {
23 |     return 'Failed to list invoices';
24 |   }
25 | };
26 | 
27 | export const listInvoicesAnnotations = () => ({
28 |   destructiveHint: false,
29 |   idempotentHint: true,
30 |   openWorldHint: true,
31 |   readOnlyHint: true,
32 |   title: 'List invoices',
33 | });
34 | 
35 | export const listInvoicesParameters = (
36 |   context: Context = {}
37 | ): z.AnyZodObject => {
38 |   const schema = z.object({
39 |     customer: z
40 |       .string()
41 |       .optional()
42 |       .describe('The ID of the customer to list invoices for.'),
43 |     limit: z
44 |       .number()
45 |       .int()
46 |       .min(1)
47 |       .max(100)
48 |       .optional()
49 |       .describe(
50 |         'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
51 |       ),
52 |   });
53 | 
54 |   if (context.customer) {
55 |     return schema.omit({customer: true});
56 |   } else {
57 |     return schema;
58 |   }
59 | };
60 | 
61 | export const listInvoicesPrompt = (context: Context = {}) => {
62 |   const customerArg = context.customer
63 |     ? `The customer is already set in the context: ${context.customer}.`
64 |     : `- customer (str, optional): The ID of the customer to list invoices for.\n`;
65 | 
66 |   return `
67 | This tool will fetch a list of Invoices from Stripe.
68 | 
69 | It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
70 | ${customerArg}
71 | - limit (int, optional): The number of invoices to return.
72 | `;
73 | };
74 | 
75 | export const finalizeInvoicePrompt = (_context: Context = {}) => `
76 | This tool will finalize an invoice in Stripe.
77 | 
78 | It takes one argument:
79 | - invoice (str): The ID of the invoice to finalize.
80 | `;
81 | 
82 | const tool = (context: Context): Tool => ({
83 |   method: 'list_invoices',
84 |   name: 'List Invoices',
85 |   description: listInvoicesPrompt(context),
86 |   parameters: listInvoicesParameters(context),
87 |   annotations: listInvoicesAnnotations(),
88 |   actions: {
89 |     invoices: {
90 |       read: true,
91 |     },
92 |   },
93 |   execute: listInvoices,
94 | });
95 | 
96 | export default tool;
97 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/products/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {createProduct} from '@/shared/products/createProduct';
 2 | import {listProducts} from '@/shared/products/listProducts';
 3 | 
 4 | const Stripe = jest.fn().mockImplementation(() => ({
 5 |   products: {
 6 |     create: jest.fn(),
 7 |     list: jest.fn(),
 8 |   },
 9 | }));
10 | 
11 | let stripe: ReturnType<typeof Stripe>;
12 | 
13 | beforeEach(() => {
14 |   stripe = new Stripe('fake-api-key');
15 | });
16 | 
17 | describe('createProduct', () => {
18 |   it('should create a product and return it', async () => {
19 |     const params = {
20 |       name: 'Test Product',
21 |     };
22 | 
23 |     const context = {};
24 | 
25 |     const mockProduct = {id: 'prod_123456', name: 'Test Product'};
26 |     stripe.products.create.mockResolvedValue(mockProduct);
27 | 
28 |     const result = await createProduct(stripe, context, params);
29 | 
30 |     expect(stripe.products.create).toHaveBeenCalledWith(params, undefined);
31 |     expect(result).toEqual(mockProduct);
32 |   });
33 | 
34 |   it('should specify the connected account if included in context', async () => {
35 |     const params = {
36 |       name: 'Test Product',
37 |     };
38 | 
39 |     const context = {
40 |       account: 'acct_123456',
41 |     };
42 | 
43 |     const mockProduct = {id: 'prod_123456', name: 'Test Product'};
44 |     stripe.products.create.mockResolvedValue(mockProduct);
45 | 
46 |     const result = await createProduct(stripe, context, params);
47 | 
48 |     expect(stripe.products.create).toHaveBeenCalledWith(params, {
49 |       stripeAccount: context.account,
50 |     });
51 |     expect(result).toEqual(mockProduct);
52 |   });
53 | });
54 | 
55 | describe('listProducts', () => {
56 |   it('should list products and return them', async () => {
57 |     const mockProducts = [
58 |       {id: 'prod_123456', name: 'Test Product 1'},
59 |       {id: 'prod_789012', name: 'Test Product 2'},
60 |     ];
61 | 
62 |     const context = {};
63 | 
64 |     stripe.products.list.mockResolvedValue({data: mockProducts});
65 |     const result = await listProducts(stripe, context, {});
66 | 
67 |     expect(stripe.products.list).toHaveBeenCalledWith({}, undefined);
68 |     expect(result).toEqual(mockProducts);
69 |   });
70 | 
71 |   it('should specify the connected account if included in context', async () => {
72 |     const mockProducts = [
73 |       {id: 'prod_123456', name: 'Test Product 1'},
74 |       {id: 'prod_789012', name: 'Test Product 2'},
75 |     ];
76 | 
77 |     const context = {
78 |       account: 'acct_123456',
79 |     };
80 | 
81 |     stripe.products.list.mockResolvedValue({data: mockProducts});
82 |     const result = await listProducts(stripe, context, {});
83 | 
84 |     expect(stripe.products.list).toHaveBeenCalledWith(
85 |       {},
86 |       {
87 |         stripeAccount: context.account,
88 |       }
89 |     );
90 |     expect(result).toEqual(mockProducts);
91 |   });
92 | });
93 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/paymentLinks/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {createPaymentLink} from '@/shared/paymentLinks/createPaymentLink';
  2 | 
  3 | const Stripe = jest.fn().mockImplementation(() => ({
  4 |   paymentLinks: {
  5 |     create: jest.fn(),
  6 |   },
  7 | }));
  8 | 
  9 | let stripe: ReturnType<typeof Stripe>;
 10 | 
 11 | beforeEach(() => {
 12 |   stripe = new Stripe('fake-api-key');
 13 | });
 14 | 
 15 | describe('createPaymentLink', () => {
 16 |   it('should create a payment link and return it', async () => {
 17 |     const params = {
 18 |       line_items: [
 19 |         {
 20 |           price: 'price_123456',
 21 |           quantity: 1,
 22 |         },
 23 |       ],
 24 |     };
 25 | 
 26 |     const mockPaymentLink = {
 27 |       id: 'pl_123456',
 28 |       url: 'https://example.com',
 29 |     };
 30 | 
 31 |     const context = {};
 32 | 
 33 |     stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
 34 | 
 35 |     const result = await createPaymentLink(stripe, context, {
 36 |       price: 'price_123456',
 37 |       quantity: 1,
 38 |     });
 39 | 
 40 |     expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
 41 |     expect(result).toEqual(mockPaymentLink);
 42 |   });
 43 | 
 44 |   it('should specify the connected account if included in context', async () => {
 45 |     const params = {
 46 |       line_items: [
 47 |         {
 48 |           price: 'price_123456',
 49 |           quantity: 1,
 50 |         },
 51 |       ],
 52 |     };
 53 | 
 54 |     const mockPaymentLink = {
 55 |       id: 'pl_123456',
 56 |       url: 'https://example.com',
 57 |     };
 58 | 
 59 |     const context = {
 60 |       account: 'acct_123456',
 61 |     };
 62 | 
 63 |     stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
 64 | 
 65 |     const result = await createPaymentLink(stripe, context, {
 66 |       price: 'price_123456',
 67 |       quantity: 1,
 68 |     });
 69 | 
 70 |     expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, {
 71 |       stripeAccount: context.account,
 72 |     });
 73 |     expect(result).toEqual(mockPaymentLink);
 74 |   });
 75 | 
 76 |   it('should specify the redirect URL if included in params', async () => {
 77 |     const params = {
 78 |       line_items: [
 79 |         {
 80 |           price: 'price_123456',
 81 |           quantity: 1,
 82 |         },
 83 |       ],
 84 |       after_completion: {
 85 |         type: 'redirect',
 86 |         redirect: {
 87 |           url: 'https://example.com',
 88 |         },
 89 |       },
 90 |     };
 91 | 
 92 |     const mockPaymentLink = {
 93 |       id: 'pl_123456',
 94 |       url: 'https://example.com',
 95 |     };
 96 | 
 97 |     const context = {};
 98 | 
 99 |     stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);
100 | 
101 |     const result = await createPaymentLink(stripe, context, {
102 |       price: 'price_123456',
103 |       quantity: 1,
104 |       redirect_url: 'https://example.com',
105 |     });
106 | 
107 |     expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
108 |     expect(result).toEqual(mockPaymentLink);
109 |   });
110 | });
111 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/documentation/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {searchDocumentation} from '@/shared/documentation/searchDocumentation';
 2 | import {z} from 'zod';
 3 | import {searchDocumentationParameters} from '@/shared/documentation/searchDocumentation';
 4 | 
 5 | const Stripe = jest.fn().mockImplementation(() => ({}));
 6 | 
 7 | let stripe: ReturnType<typeof Stripe>;
 8 | 
 9 | beforeEach(() => {
10 |   stripe = new Stripe('fake-api-key');
11 | });
12 | 
13 | const EXPECTED_HEADERS = {
14 |   'Content-Type': 'application/json',
15 |   'X-Requested-With': 'fetch',
16 |   'User-Agent': 'stripe-agent-toolkit-typescript',
17 | };
18 | 
19 | describe('searchDocumentation', () => {
20 |   it('should search for Stripe documentation and return sources', async () => {
21 |     const question = 'How to create Stripe checkout session?';
22 |     const requestBody: z.infer<
23 |       ReturnType<typeof searchDocumentationParameters>
24 |     > = {
25 |       question: question,
26 |       language: 'ruby',
27 |     };
28 | 
29 |     const sources = [
30 |       {
31 |         type: 'docs',
32 |         url: 'https://docs.stripe.com/payments/checkout/how-checkout-works',
33 |         title: 'How checkout works',
34 |         content: '...',
35 |       },
36 |     ];
37 |     const mockResponse = {
38 |       question: question,
39 |       status: 'success',
40 |       sources: sources,
41 |     };
42 | 
43 |     const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
44 |       ok: true,
45 |       status: 200,
46 |       json: jest.fn().mockResolvedValueOnce(mockResponse),
47 |     } as unknown as Response);
48 | 
49 |     const result = await searchDocumentation(stripe, {}, requestBody);
50 | 
51 |     expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
52 |       method: 'POST',
53 |       headers: EXPECTED_HEADERS,
54 |       body: JSON.stringify(requestBody),
55 |     });
56 | 
57 |     expect(result).toEqual(sources);
58 |   });
59 | 
60 |   it('should return failure string if search failed', async () => {
61 |     const question = 'What is the meaning of life?';
62 |     const requestBody = {
63 |       question: question,
64 |     };
65 | 
66 |     const mockError = {
67 |       error: 'Invalid query',
68 |       message:
69 |         'Unable to process your question. Please rephrase it to be more specific.',
70 |     };
71 | 
72 |     const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
73 |       ok: false,
74 |       status: 400,
75 |       json: jest.fn().mockResolvedValueOnce(mockError),
76 |     } as unknown as Response);
77 | 
78 |     const result = await searchDocumentation(stripe, {}, requestBody);
79 | 
80 |     expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
81 |       method: 'POST',
82 |       headers: EXPECTED_HEADERS,
83 |       body: JSON.stringify(requestBody),
84 |     });
85 | 
86 |     expect(result).toEqual('Failed to search documentation');
87 |   });
88 | });
89 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/documentation/searchDocumentation.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Stripe from 'stripe';
 2 | import {z} from 'zod';
 3 | import type {Context} from '@/shared/configuration';
 4 | import type {Tool} from '@/shared/tools';
 5 | export const searchDocumentationPrompt = (_context: Context = {}) => `
 6 | This tool will take in a user question about integrating with Stripe in their application, then search and retrieve relevant Stripe documentation to answer the question.
 7 | 
 8 | It takes two arguments:
 9 | - question (str): The user question to search an answer for in the Stripe documentation.
10 | - language (str, optional): The programming language to search for in the the documentation.
11 | `;
12 | 
13 | export const searchDocumentationParameters = (
14 |   _context: Context = {}
15 | ): z.AnyZodObject =>
16 |   z.object({
17 |     question: z
18 |       .string()
19 |       .describe(
20 |         'The user question about integrating with Stripe will be used to search the documentation.'
21 |       ),
22 |     language: z
23 |       .enum(['dotnet', 'go', 'java', 'node', 'php', 'ruby', 'python', 'curl'])
24 |       .optional()
25 |       .describe(
26 |         'The programming language to search for in the the documentation.'
27 |       ),
28 |   });
29 | 
30 | export const searchDocumentationAnnotations = () => ({
31 |   destructiveHint: false,
32 |   idempotentHint: true,
33 |   openWorldHint: true,
34 |   readOnlyHint: true,
35 |   title: 'Search Stripe documentation',
36 | });
37 | 
38 | export const searchDocumentation = async (
39 |   _stripe: Stripe,
40 |   context: Context,
41 |   params: z.infer<ReturnType<typeof searchDocumentationParameters>>
42 | ) => {
43 |   try {
44 |     const endpoint = 'https://ai.stripe.com/search';
45 |     const response = await fetch(endpoint, {
46 |       method: 'POST',
47 |       headers: {
48 |         'Content-Type': 'application/json',
49 |         'X-Requested-With': 'fetch',
50 |         'User-Agent':
51 |           context.mode === 'modelcontextprotocol'
52 |             ? 'stripe-mcp'
53 |             : 'stripe-agent-toolkit-typescript',
54 |       },
55 |       body: JSON.stringify(params),
56 |     });
57 | 
58 |     // If status not in 200-299 range, throw error
59 |     if (!response.ok) {
60 |       throw new Error(`HTTP error! Status: ${response.status}`);
61 |     }
62 | 
63 |     const data = await response.json();
64 |     return data?.sources;
65 |   } catch (error) {
66 |     return 'Failed to search documentation';
67 |   }
68 | };
69 | 
70 | const tool = (context: Context): Tool => ({
71 |   method: 'search_stripe_documentation',
72 |   name: 'Search Stripe Documentation',
73 |   description: searchDocumentationPrompt(context),
74 |   parameters: searchDocumentationParameters(context),
75 |   annotations: searchDocumentationAnnotations(),
76 |   actions: {
77 |     documentation: {
78 |       read: true,
79 |     },
80 |   },
81 |   execute: searchDocumentation,
82 | });
83 | 
84 | export default tool;
85 | 
```

--------------------------------------------------------------------------------
/typescript/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@stripe/agent-toolkit",
 3 |   "version": "0.7.11",
 4 |   "homepage": "https://github.com/stripe/agent-toolkit",
 5 |   "scripts": {
 6 |     "build": "tsup",
 7 |     "clean": "rm -rf langchain ai-sdk modelcontextprotocol openai cloudflare",
 8 |     "lint": "eslint \"./**/*.ts*\"",
 9 |     "prettier": "prettier './**/*.{js,ts,md,html,css}' --write",
10 |     "prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check",
11 |     "test": "jest",
12 |     "eval": "cd ../evals && pnpm test"
13 |   },
14 |   "exports": {
15 |     "./langchain": {
16 |       "types": "./langchain/index.d.ts",
17 |       "require": "./langchain/index.js",
18 |       "import": "./langchain/index.mjs"
19 |     },
20 |     "./ai-sdk": {
21 |       "types": "./ai-sdk/index.d.ts",
22 |       "require": "./ai-sdk/index.js",
23 |       "import": "./ai-sdk/index.mjs"
24 |     },
25 |     "./openai": {
26 |       "types": "./openai/index.d.ts",
27 |       "require": "./openai/index.js",
28 |       "import": "./openai/index.mjs"
29 |     },
30 |     "./modelcontextprotocol": {
31 |       "types": "./modelcontextprotocol/index.d.ts",
32 |       "require": "./modelcontextprotocol/index.js",
33 |       "import": "./modelcontextprotocol/index.mjs"
34 |     },
35 |     "./cloudflare": {
36 |       "types": "./cloudflare/index.d.ts",
37 |       "require": "./cloudflare/index.js",
38 |       "import": "./cloudflare/index.mjs"
39 |     }
40 |   },
41 |   "packageManager": "[email protected]",
42 |   "engines": {
43 |     "node": ">=18"
44 |   },
45 |   "author": "Stripe <[email protected]> (https://stripe.com/)",
46 |   "contributors": [
47 |     "Steve Kaliski <[email protected]>"
48 |   ],
49 |   "license": "MIT",
50 |   "devDependencies": {
51 |     "@eslint/compat": "^1.2.4",
52 |     "@types/jest": "^29.5.14",
53 |     "@types/node": "^22.10.5",
54 |     "@typescript-eslint/eslint-plugin": "^8.19.1",
55 |     "eslint": "^9.17.0",
56 |     "eslint-config-prettier": "^9.1.0",
57 |     "eslint-plugin-import": "^2.31.0",
58 |     "eslint-plugin-jest": "^28.10.0",
59 |     "eslint-plugin-prettier": "^5.2.1",
60 |     "globals": "^15.14.0",
61 |     "jest": "^29.7.0",
62 |     "prettier": "^3.4.2",
63 |     "ts-jest": "^29.2.5",
64 |     "ts-node": "^10.9.2",
65 |     "tsup": "^8.3.5",
66 |     "typescript": "^5.8.3"
67 |   },
68 |   "dependencies": {
69 |     "stripe": "^17.5.0",
70 |     "zod": "^3.24.1",
71 |     "zod-to-json-schema": "^3.24.3"
72 |   },
73 |   "peerDependencies": {
74 |     "openai": "^4.86.1",
75 |     "@langchain/core": "^0.3.6",
76 |     "@modelcontextprotocol/sdk": "^1.17.1",
77 |     "ai": "^3.4.7 || ^4.0.0",
78 |     "agents": "^0.0.84"
79 |   },
80 |   "workspaces": [
81 |     ".",
82 |     "examples/*"
83 |   ],
84 |   "files": [
85 |     "ai-sdk/**/*",
86 |     "langchain/**/*",
87 |     "modelcontextprotocol/**/*",
88 |     "openai/**/*",
89 |     "cloudflare/**/*",
90 |     "LICENSE",
91 |     "README.md",
92 |     "VERSION",
93 |     "package.json"
94 |   ]
95 | }
96 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/prices/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {createPrice} from '@/shared/prices/createPrice';
 2 | import {listPrices} from '@/shared/prices/listPrices';
 3 | 
 4 | const Stripe = jest.fn().mockImplementation(() => ({
 5 |   prices: {
 6 |     create: jest.fn(),
 7 |     list: jest.fn(),
 8 |   },
 9 | }));
10 | 
11 | let stripe: ReturnType<typeof Stripe>;
12 | 
13 | beforeEach(() => {
14 |   stripe = new Stripe('fake-api-key');
15 | });
16 | 
17 | describe('createPrice', () => {
18 |   it('should create a price and return it', async () => {
19 |     const params = {
20 |       unit_amount: 1000,
21 |       currency: 'usd',
22 |       product: 'prod_123456',
23 |     };
24 | 
25 |     const context = {};
26 | 
27 |     const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
28 |     stripe.prices.create.mockResolvedValue(mockPrice);
29 | 
30 |     const result = await createPrice(stripe, context, params);
31 | 
32 |     expect(stripe.prices.create).toHaveBeenCalledWith(params, undefined);
33 |     expect(result).toEqual(mockPrice);
34 |   });
35 | 
36 |   it('should specify the connected account if included in context', async () => {
37 |     const params = {
38 |       unit_amount: 1000,
39 |       currency: 'usd',
40 |       product: 'prod_123456',
41 |     };
42 | 
43 |     const context = {
44 |       account: 'acct_123456',
45 |     };
46 | 
47 |     const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
48 |     stripe.prices.create.mockResolvedValue(mockPrice);
49 | 
50 |     const result = await createPrice(stripe, context, params);
51 | 
52 |     expect(stripe.prices.create).toHaveBeenCalledWith(params, {
53 |       stripeAccount: context.account,
54 |     });
55 |     expect(result).toEqual(mockPrice);
56 |   });
57 | });
58 | 
59 | describe('listPrices', () => {
60 |   it('should list prices and return them', async () => {
61 |     const mockPrices = [
62 |       {id: 'price_123456', unit_amount: 1000, currency: 'usd'},
63 |       {id: 'price_789012', unit_amount: 2000, currency: 'usd'},
64 |     ];
65 | 
66 |     const context = {};
67 | 
68 |     stripe.prices.list.mockResolvedValue({data: mockPrices});
69 |     const result = await listPrices(stripe, context, {});
70 | 
71 |     expect(stripe.prices.list).toHaveBeenCalledWith({}, undefined);
72 |     expect(result).toEqual(mockPrices);
73 |   });
74 | 
75 |   it('should specify the connected account if included in context', async () => {
76 |     const mockPrices = [
77 |       {id: 'price_123456', unit_amount: 1000, currency: 'usd'},
78 |       {id: 'price_789012', unit_amount: 2000, currency: 'usd'},
79 |     ];
80 | 
81 |     const context = {
82 |       account: 'acct_123456',
83 |     };
84 | 
85 |     stripe.prices.list.mockResolvedValue({data: mockPrices});
86 |     const result = await listPrices(stripe, context, {});
87 | 
88 |     expect(stripe.prices.list).toHaveBeenCalledWith(
89 |       {},
90 |       {
91 |         stripeAccount: context.account,
92 |       }
93 |     );
94 |     expect(result).toEqual(mockPrices);
95 |   });
96 | });
97 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/customers/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {createCustomer} from '@/shared/customers/createCustomer';
 2 | import {listCustomers} from '@/shared/customers/listCustomers';
 3 | 
 4 | const Stripe = jest.fn().mockImplementation(() => ({
 5 |   customers: {
 6 |     create: jest.fn(),
 7 |     list: jest.fn(),
 8 |   },
 9 | }));
10 | 
11 | let stripe: ReturnType<typeof Stripe>;
12 | 
13 | beforeEach(() => {
14 |   stripe = new Stripe('fake-api-key');
15 | });
16 | 
17 | describe('createCustomer', () => {
18 |   it('should create a customer and return the id', async () => {
19 |     const params = {
20 |       email: '[email protected]',
21 |       name: 'Test User',
22 |     };
23 | 
24 |     const context = {};
25 | 
26 |     const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
27 |     stripe.customers.create.mockResolvedValue(mockCustomer);
28 | 
29 |     const result = await createCustomer(stripe, context, params);
30 | 
31 |     expect(stripe.customers.create).toHaveBeenCalledWith(params, undefined);
32 |     expect(result).toEqual({id: mockCustomer.id});
33 |   });
34 | 
35 |   it('should specify the connected account if included in context', async () => {
36 |     const params = {
37 |       email: '[email protected]',
38 |       name: 'Test User',
39 |     };
40 | 
41 |     const context = {
42 |       account: 'acct_123456',
43 |     };
44 | 
45 |     const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
46 |     stripe.customers.create.mockResolvedValue(mockCustomer);
47 | 
48 |     const result = await createCustomer(stripe, context, params);
49 | 
50 |     expect(stripe.customers.create).toHaveBeenCalledWith(params, {
51 |       stripeAccount: context.account,
52 |     });
53 |     expect(result).toEqual({id: mockCustomer.id});
54 |   });
55 | });
56 | 
57 | describe('listCustomers', () => {
58 |   it('should list customers and return their ids', async () => {
59 |     const mockCustomers = [
60 |       {id: 'cus_123456', email: '[email protected]'},
61 |       {id: 'cus_789012', email: '[email protected]'},
62 |     ];
63 | 
64 |     const context = {};
65 | 
66 |     stripe.customers.list.mockResolvedValue({data: mockCustomers});
67 |     const result = await listCustomers(stripe, context, {});
68 | 
69 |     expect(stripe.customers.list).toHaveBeenCalledWith({}, undefined);
70 |     expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
71 |   });
72 | 
73 |   it('should specify the connected account if included in context', async () => {
74 |     const mockCustomers = [
75 |       {id: 'cus_123456', email: '[email protected]'},
76 |       {id: 'cus_789012', email: '[email protected]'},
77 |     ];
78 | 
79 |     const context = {
80 |       account: 'acct_123456',
81 |     };
82 | 
83 |     stripe.customers.list.mockResolvedValue({data: mockCustomers});
84 |     const result = await listCustomers(stripe, context, {});
85 | 
86 |     expect(stripe.customers.list).toHaveBeenCalledWith(
87 |       {},
88 |       {
89 |         stripeAccount: context.account,
90 |       }
91 |     );
92 |     expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
93 |   });
94 | });
95 | 
```

--------------------------------------------------------------------------------
/python/examples/openai/customer_support/main.py:
--------------------------------------------------------------------------------

```python
 1 | import env
 2 | import asyncio
 3 | from emailer import Emailer, Email
 4 | from typing import Union, List
 5 | import support_agent
 6 | import markdown as markdown
 7 | import json
 8 | 
 9 | from agents import (
10 |     ItemHelpers,
11 |     TResponseInputItem,
12 | )
13 | 
14 | 
15 | env.ensure("STRIPE_SECRET_KEY")
16 | env.ensure("OPENAI_API_KEY")
17 | 
18 | email_address = env.ensure("EMAIL_ADDRESS")
19 | support_address = env.get_or("SUPPORT_ADDRESS", email_address)
20 | email_password = env.ensure("EMAIL_PASSWORD")
21 | emailer = Emailer(email_address, email_password, support_address)
22 | 
23 | 
24 | def unsure(str: str) -> bool:
25 |     return (
26 |         "not sure" in str
27 |         or "unsure" in str
28 |         or "don't know" in str
29 |         or "dont know" in str
30 |         or "do not know" in str
31 |     )
32 | 
33 | 
34 | async def respond(thread: List[Email]) -> Union[Email, None]:
35 |     most_recent = thread[-1]
36 |     print(f"Got unread email:\n  {json.dumps(most_recent.to_dict())}")
37 | 
38 |     # Loop through the entire thread to add historical context for the agent
39 |     input_items: list[TResponseInputItem] = []
40 |     for email in thread:
41 |         input_items.append(
42 |             {
43 |                 "content": (
44 |                     "This is an earlier email:"
45 |                     f"Email from: {email.from_address}\n"
46 |                     f"To: {email.to_address}\n"
47 |                     f"Subject: {email.subject}\n\n"
48 |                     f"{email.body}"
49 |                 ),
50 |                 "role": "user",
51 |             }
52 |         )
53 | 
54 |     input_items.append(
55 |         {
56 |             "content": (
57 |                 "This the latest email"
58 |                 "You can use context from earlier emails"
59 |                 "but reply specifically to the following email:"
60 |                 f"Email from: {most_recent.from_address}\n"
61 |                 f"To: {most_recent.to_address}\n"
62 |                 f"Subject: {most_recent.subject}\n\n"
63 |                 f"{most_recent.body}"
64 |             ),
65 |             "role": "user",
66 |         }
67 |     )
68 | 
69 |     print(f"Sending to agent:\n  {json.dumps(input_items)}")
70 | 
71 |     output = await support_agent.run(input_items)
72 |     body_md = ItemHelpers.text_message_outputs(output.new_items)
73 | 
74 |     # Handle answers that the agent doesn't know
75 |     if unsure(body_md.lower()):
76 |         print(
77 |             f"Agent doesn't know, ignore response and keep email in the inbox:\n{body_md}"
78 |         )
79 |         return None
80 | 
81 |     # OpenAI often returns the body in html fences, trim those
82 |     body_html = markdown.markdown(body_md, extensions=["tables"])
83 | 
84 |     return Email(
85 |         from_address=most_recent.to_address,
86 |         to_address=most_recent.from_address,
87 |         subject=most_recent.subject,
88 |         body=body_html,
89 |     )
90 | 
91 | 
92 | async def main():
93 |     await emailer.run(respond, delay=30, mark_read=True)
94 | 
95 | 
96 | if __name__ == "__main__":
97 |     asyncio.run(main())
98 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/configuration.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import z from 'zod';
  2 | import {isToolAllowed} from '@/shared/configuration';
  3 | 
  4 | describe('isToolAllowed', () => {
  5 |   it('should return true if all permissions are allowed', () => {
  6 |     const tool = {
  7 |       method: 'test',
  8 |       name: 'Test',
  9 |       description: 'Test',
 10 |       parameters: z.object({
 11 |         foo: z.string(),
 12 |       }),
 13 |       annotations: {
 14 |         destructiveHint: false,
 15 |         idempotentHint: false,
 16 |         openWorldHint: true,
 17 |         readOnlyHint: false,
 18 |         title: 'Test',
 19 |       },
 20 |       execute: async (a: any, b: any, c: any) => {},
 21 |       actions: {
 22 |         customers: {
 23 |           create: true,
 24 |           read: true,
 25 |         },
 26 |         invoices: {
 27 |           create: true,
 28 |           read: true,
 29 |         },
 30 |       },
 31 |     };
 32 | 
 33 |     const configuration = {
 34 |       actions: {
 35 |         customers: {
 36 |           create: true,
 37 |           read: true,
 38 |         },
 39 |         invoices: {
 40 |           create: true,
 41 |           read: true,
 42 |         },
 43 |       },
 44 |     };
 45 | 
 46 |     expect(isToolAllowed(tool, configuration)).toBe(true);
 47 |   });
 48 | 
 49 |   it('should return false if any permission is denied', () => {
 50 |     const tool = {
 51 |       method: 'test',
 52 |       name: 'Test',
 53 |       description: 'Test',
 54 |       parameters: z.object({
 55 |         foo: z.string(),
 56 |       }),
 57 |       annotations: {
 58 |         destructiveHint: false,
 59 |         idempotentHint: false,
 60 |         openWorldHint: true,
 61 |         readOnlyHint: false,
 62 |         title: 'Test',
 63 |       },
 64 |       execute: async (a: any, b: any, c: any) => {},
 65 |       actions: {
 66 |         customers: {
 67 |           create: true,
 68 |           read: true,
 69 |         },
 70 |         invoices: {
 71 |           create: true,
 72 |           read: true,
 73 |         },
 74 |       },
 75 |     };
 76 | 
 77 |     const configuration = {
 78 |       actions: {
 79 |         customers: {
 80 |           create: true,
 81 |           read: true,
 82 |         },
 83 |         invoices: {
 84 |           create: true,
 85 |           read: false,
 86 |         },
 87 |       },
 88 |     };
 89 | 
 90 |     expect(isToolAllowed(tool, configuration)).toBe(false);
 91 |   });
 92 | 
 93 |   it('should return false if any resource is not allowed', () => {
 94 |     const tool = {
 95 |       method: 'test',
 96 |       name: 'Test',
 97 |       description: 'Test',
 98 |       parameters: z.object({
 99 |         foo: z.string(),
100 |       }),
101 |       annotations: {
102 |         destructiveHint: false,
103 |         idempotentHint: false,
104 |         openWorldHint: true,
105 |         readOnlyHint: false,
106 |         title: 'Test',
107 |       },
108 |       execute: async (a: any, b: any, c: any) => {},
109 |       actions: {
110 |         paymentLinks: {
111 |           create: true,
112 |         },
113 |       },
114 |     };
115 | 
116 |     const configuration = {
117 |       actions: {
118 |         customers: {
119 |           create: true,
120 |           read: true,
121 |         },
122 |         invoices: {
123 |           create: true,
124 |           read: true,
125 |         },
126 |       },
127 |     };
128 | 
129 |     expect(isToolAllowed(tool, configuration)).toBe(false);
130 |   });
131 | });
132 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/subscriptions/listSubscriptions.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {Tool} from '@/shared/tools';
  5 | 
  6 | export const listSubscriptions = async (
  7 |   stripe: Stripe,
  8 |   context: Context,
  9 |   params: z.infer<ReturnType<typeof listSubscriptionsParameters>>
 10 | ) => {
 11 |   try {
 12 |     if (context.customer) {
 13 |       params.customer = context.customer;
 14 |     }
 15 | 
 16 |     const subscriptions = await stripe.subscriptions.list(
 17 |       params,
 18 |       context.account ? {stripeAccount: context.account} : undefined
 19 |     );
 20 | 
 21 |     return subscriptions.data;
 22 |   } catch (error) {
 23 |     return 'Failed to list subscriptions';
 24 |   }
 25 | };
 26 | 
 27 | export const listSubscriptionsParameters = (
 28 |   context: Context = {}
 29 | ): z.AnyZodObject => {
 30 |   const schema = z.object({
 31 |     customer: z
 32 |       .string()
 33 |       .optional()
 34 |       .describe('The ID of the customer to list subscriptions for.'),
 35 |     price: z
 36 |       .string()
 37 |       .optional()
 38 |       .describe('The ID of the price to list subscriptions for.'),
 39 |     status: z
 40 |       .enum([
 41 |         'active',
 42 |         'past_due',
 43 |         'unpaid',
 44 |         'canceled',
 45 |         'incomplete',
 46 |         'incomplete_expired',
 47 |         'trialing',
 48 |         'all',
 49 |       ])
 50 |       .optional()
 51 |       .describe('The status of the subscriptions to retrieve.'),
 52 |     limit: z
 53 |       .number()
 54 |       .int()
 55 |       .min(1)
 56 |       .max(100)
 57 |       .optional()
 58 |       .describe(
 59 |         'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
 60 |       ),
 61 |   });
 62 | 
 63 |   if (context.customer) {
 64 |     return schema.omit({customer: true});
 65 |   } else {
 66 |     return schema;
 67 |   }
 68 | };
 69 | 
 70 | export const listSubscriptionsPrompt = (context: Context = {}): string => {
 71 |   const customerArg = context.customer
 72 |     ? `The customer is already set in the context: ${context.customer}.`
 73 |     : `- customer (str, optional): The ID of the customer to list subscriptions for.\n`;
 74 | 
 75 |   return `
 76 | This tool will list all subscriptions in Stripe.
 77 | 
 78 | It takes ${context.customer ? 'three' : 'four'} arguments:
 79 | ${customerArg}
 80 | - price (str, optional): The ID of the price to list subscriptions for.
 81 | - status (str, optional): The status of the subscriptions to list.
 82 | - limit (int, optional): The number of subscriptions to return.
 83 | `;
 84 | };
 85 | 
 86 | export const listSubscriptionsAnnotations = () => ({
 87 |   destructiveHint: false,
 88 |   idempotentHint: true,
 89 |   openWorldHint: true,
 90 |   readOnlyHint: true,
 91 |   title: 'List subscriptions',
 92 | });
 93 | 
 94 | const tool = (context: Context): Tool => ({
 95 |   method: 'list_subscriptions',
 96 |   name: 'List Subscriptions',
 97 |   description: listSubscriptionsPrompt(context),
 98 |   parameters: listSubscriptionsParameters(context),
 99 |   annotations: listSubscriptionsAnnotations(),
100 |   actions: {
101 |     subscriptions: {
102 |       read: true,
103 |     },
104 |   },
105 |   execute: listSubscriptions,
106 | });
107 | 
108 | export default tool;
109 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/tools.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {z} from 'zod';
 2 | 
 3 | import createCustomerTool from '@/shared/customers/createCustomer';
 4 | import listCustomersTool from '@/shared/customers/listCustomers';
 5 | import createProductTool from '@/shared/products/createProduct';
 6 | import listProductsTool from '@/shared/products/listProducts';
 7 | import createPriceTool from '@/shared/prices/createPrice';
 8 | import listPricesTool from '@/shared/prices/listPrices';
 9 | import createPaymentLinkTool from '@/shared/paymentLinks/createPaymentLink';
10 | import createInvoiceTool from '@/shared/invoices/createInvoice';
11 | import listInvoicesTool from '@/shared/invoices/listInvoices';
12 | import createInvoiceItemTool from '@/shared/invoiceItems/createInvoiceItem';
13 | import finalizeInvoiceTool from '@/shared/invoices/finalizeInvoice';
14 | import retrieveBalanceTool from '@/shared/balance/retrieveBalance';
15 | import listCouponsTool from '@/shared/coupons/listCoupons';
16 | import createCouponTool from '@/shared/coupons/createCoupon';
17 | import createRefundTool from '@/shared/refunds/createRefund';
18 | import listPaymentIntentsTool from '@/shared/paymentIntents/listPaymentIntents';
19 | import listSubscriptionsTool from '@/shared/subscriptions/listSubscriptions';
20 | import cancelSubscriptionTool from '@/shared/subscriptions/cancelSubscription';
21 | import updateSubscriptionTool from '@/shared/subscriptions/updateSubscription';
22 | import searchDocumentationTool from '@/shared/documentation/searchDocumentation';
23 | import listDisputesTool from '@/shared/disputes/listDisputes';
24 | import updateDisputeTool from '@/shared/disputes/updateDispute';
25 | 
26 | import {Context} from './configuration';
27 | import Stripe from 'stripe';
28 | 
29 | export type Tool = {
30 |   method: string;
31 |   name: string;
32 |   description: string;
33 |   parameters: z.ZodObject<any, any, any, any>;
34 |   annotations: {
35 |     destructiveHint?: boolean;
36 |     idempotentHint?: boolean;
37 |     openWorldHint?: boolean;
38 |     readOnlyHint?: boolean;
39 |     title?: string;
40 |   };
41 |   actions: {
42 |     [key: string]: {
43 |       [action: string]: boolean;
44 |     };
45 |   };
46 |   execute: (stripe: Stripe, context: Context, params: any) => Promise<any>;
47 | };
48 | 
49 | const tools = (context: Context): Tool[] => [
50 |   createCustomerTool(context),
51 |   listCustomersTool(context),
52 |   createProductTool(context),
53 |   listProductsTool(context),
54 |   createPriceTool(context),
55 |   listPricesTool(context),
56 |   createPaymentLinkTool(context),
57 |   createInvoiceTool(context),
58 |   listInvoicesTool(context),
59 |   createInvoiceItemTool(context),
60 |   finalizeInvoiceTool(context),
61 |   retrieveBalanceTool(context),
62 |   createRefundTool(context),
63 |   listPaymentIntentsTool(context),
64 |   listSubscriptionsTool(context),
65 |   cancelSubscriptionTool(context),
66 |   updateSubscriptionTool(context),
67 |   searchDocumentationTool(context),
68 |   listCouponsTool(context),
69 |   createCouponTool(context),
70 |   updateDisputeTool(context),
71 |   listDisputesTool(context),
72 | ];
73 | 
74 | export default tools;
75 | 
```

--------------------------------------------------------------------------------
/typescript/src/ai-sdk/toolkit.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import StripeAPI from '../shared/api';
  2 | import tools from '../shared/tools';
  3 | import {isToolAllowed, type Configuration} from '../shared/configuration';
  4 | import type {
  5 |   CoreTool,
  6 |   LanguageModelV1StreamPart,
  7 |   Experimental_LanguageModelV1Middleware as LanguageModelV1Middleware,
  8 | } from 'ai';
  9 | import StripeTool from './tool';
 10 | 
 11 | type StripeMiddlewareConfig = {
 12 |   billing?: {
 13 |     type?: 'token';
 14 |     customer: string;
 15 |     meters: {
 16 |       input?: string;
 17 |       output?: string;
 18 |     };
 19 |   };
 20 | };
 21 | 
 22 | class StripeAgentToolkit {
 23 |   private _stripe: StripeAPI;
 24 | 
 25 |   tools: {[key: string]: CoreTool};
 26 | 
 27 |   constructor({
 28 |     secretKey,
 29 |     configuration,
 30 |   }: {
 31 |     secretKey: string;
 32 |     configuration: Configuration;
 33 |   }) {
 34 |     this._stripe = new StripeAPI(secretKey, configuration.context);
 35 |     this.tools = {};
 36 | 
 37 |     const context = configuration.context || {};
 38 |     const filteredTools = tools(context).filter((tool) =>
 39 |       isToolAllowed(tool, configuration)
 40 |     );
 41 | 
 42 |     filteredTools.forEach((tool) => {
 43 |       // @ts-ignore
 44 |       this.tools[tool.method] = StripeTool(
 45 |         this._stripe,
 46 |         tool.method,
 47 |         tool.description,
 48 |         tool.parameters
 49 |       );
 50 |     });
 51 |   }
 52 | 
 53 |   middleware(config: StripeMiddlewareConfig): LanguageModelV1Middleware {
 54 |     const bill = async ({
 55 |       promptTokens,
 56 |       completionTokens,
 57 |     }: {
 58 |       promptTokens: number;
 59 |       completionTokens: number;
 60 |     }) => {
 61 |       if (config.billing) {
 62 |         if (config.billing.meters.input) {
 63 |           await this._stripe.createMeterEvent({
 64 |             event: config.billing.meters.input,
 65 |             customer: config.billing.customer,
 66 |             value: promptTokens.toString(),
 67 |           });
 68 |         }
 69 |         if (config.billing.meters.output) {
 70 |           await this._stripe.createMeterEvent({
 71 |             event: config.billing.meters.output,
 72 |             customer: config.billing.customer,
 73 |             value: completionTokens.toString(),
 74 |           });
 75 |         }
 76 |       }
 77 |     };
 78 | 
 79 |     return {
 80 |       wrapGenerate: async ({doGenerate}) => {
 81 |         const result = await doGenerate();
 82 | 
 83 |         if (config.billing) {
 84 |           await bill(result.usage);
 85 |         }
 86 | 
 87 |         return result;
 88 |       },
 89 | 
 90 |       wrapStream: async ({doStream}) => {
 91 |         const {stream, ...rest} = await doStream();
 92 | 
 93 |         const transformStream = new TransformStream<
 94 |           LanguageModelV1StreamPart,
 95 |           LanguageModelV1StreamPart
 96 |         >({
 97 |           async transform(chunk, controller) {
 98 |             if (chunk.type === 'finish') {
 99 |               if (config.billing) {
100 |                 await bill(chunk.usage);
101 |               }
102 |             }
103 | 
104 |             controller.enqueue(chunk);
105 |           },
106 |         });
107 | 
108 |         return {
109 |           stream: stream.pipeThrough(transformStream),
110 |           ...rest,
111 |         };
112 |       },
113 |     };
114 |   }
115 | 
116 |   getTools(): {[key: string]: CoreTool} {
117 |     return this.tools;
118 |   }
119 | }
120 | 
121 | export default StripeAgentToolkit;
122 | 
```

--------------------------------------------------------------------------------
/evals/eval.ts:
--------------------------------------------------------------------------------

```typescript
  1 | require("dotenv").config();
  2 | 
  3 | import { StripeAgentToolkit } from "../typescript/src/openai";
  4 | import type {
  5 |   ChatCompletion,
  6 |   ChatCompletionMessageParam,
  7 | } from "openai/resources";
  8 | import { Eval } from "braintrust";
  9 | import { AssertionScorer, EvalCaseFunction, EvalInput } from "./scorer";
 10 | import { getEvalTestCases } from "./cases";
 11 | import braintrustOpenai from "./braintrust_openai";
 12 | 
 13 | // This is the core "workhorse" function that accepts an input and returns a response
 14 | // which calls stripe agent tookit
 15 | async function task(evalInput: EvalInput): Promise<EvalOutput> {
 16 |   const stripeAgentToolkit = new StripeAgentToolkit({
 17 |     secretKey: process.env.STRIPE_SECRET_KEY!,
 18 |     configuration: {
 19 |       actions: {
 20 |         paymentLinks: {
 21 |           create: true,
 22 |           read: true,
 23 |         },
 24 |         products: {
 25 |           create: true,
 26 |           read: true,
 27 |         },
 28 |         prices: {
 29 |           create: true,
 30 |           read: true,
 31 |         },
 32 |         coupons: {
 33 |           create: true,
 34 |           read: true,
 35 |         },
 36 |         customers: {
 37 |           create: true,
 38 |           read: true,
 39 |         },
 40 |         paymentIntents: {
 41 |           create: true,
 42 |           read: true,
 43 |         },
 44 |         invoices: {
 45 |           create: true,
 46 |           read: true,
 47 |         },
 48 |         invoiceItems: {
 49 |           create: true,
 50 |           read: true,
 51 |         },
 52 |         refunds: {
 53 |           create: true,
 54 |           read: true,
 55 |         },
 56 |         subscriptions: {
 57 |           read: true,
 58 |           update: true,
 59 |         },
 60 |         balance: {
 61 |           read: true,
 62 |         },
 63 |         disputes: {
 64 |           read: true,
 65 |           update: true,
 66 |         },
 67 |       },
 68 |       ...evalInput.toolkitConfigOverride,
 69 |     },
 70 |   });
 71 | 
 72 |   let messages: ChatCompletionMessageParam[] = [
 73 |     {
 74 |       role: "user",
 75 |       content: evalInput.userPrompt,
 76 |     },
 77 |   ];
 78 | 
 79 |   let completion: ChatCompletion;
 80 | 
 81 |   const tools = stripeAgentToolkit.getTools();
 82 | 
 83 |   while (true) {
 84 |     // eslint-disable-next-line no-await-in-loop
 85 |     completion = await braintrustOpenai.chat.completions.create({
 86 |       model: "gpt-4o",
 87 |       messages,
 88 |       tools,
 89 |     });
 90 | 
 91 |     const message = completion.choices[0].message;
 92 | 
 93 |     messages.push(message);
 94 | 
 95 |     if (message.tool_calls?.length! > 0) {
 96 |       // eslint-disable-next-line no-await-in-loop
 97 |       const toolMessages = await Promise.all(
 98 |         message.tool_calls!.map((tc) => stripeAgentToolkit.handleToolCall(tc))
 99 |       );
100 | 
101 |       messages = [...messages, ...toolMessages];
102 |     } else {
103 |       break;
104 |     }
105 |   }
106 | 
107 |   return messages;
108 | }
109 | 
110 | const BRAINTRUST_PROJECT = "agent-toolkit";
111 | 
112 | export type EvalOutput = ChatCompletionMessageParam[];
113 | 
114 | async function main() {
115 |   await Eval<EvalInput, EvalOutput, EvalCaseFunction>(BRAINTRUST_PROJECT, {
116 |     data: await getEvalTestCases(),
117 |     task: async (input) => {
118 |       const result = await task(input);
119 |       return result;
120 |     },
121 |     scores: [AssertionScorer],
122 |   });
123 | }
124 | 
125 | main();
126 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/paymentIntents/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {listPaymentIntents} from '@/shared/paymentIntents/listPaymentIntents';
  2 | 
  3 | const Stripe = jest.fn().mockImplementation(() => ({
  4 |   paymentIntents: {
  5 |     list: jest.fn(),
  6 |   },
  7 | }));
  8 | 
  9 | let stripe: ReturnType<typeof Stripe>;
 10 | 
 11 | beforeEach(() => {
 12 |   stripe = new Stripe('fake-api-key');
 13 | });
 14 | 
 15 | describe('listPaymentIntents', () => {
 16 |   it('should list payment intents and return them', async () => {
 17 |     const mockPaymentIntents = [
 18 |       {
 19 |         id: 'pi_123456',
 20 |         customer: 'cus_123456',
 21 |         amount: 1000,
 22 |         status: 'succeeded',
 23 |         description: 'Test Payment Intent',
 24 |       },
 25 |     ];
 26 | 
 27 |     const context = {};
 28 | 
 29 |     stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
 30 | 
 31 |     const result = await listPaymentIntents(stripe, context, {});
 32 | 
 33 |     expect(stripe.paymentIntents.list).toHaveBeenCalledWith({}, undefined);
 34 |     expect(result).toEqual(mockPaymentIntents);
 35 |   });
 36 | 
 37 |   it('should list payment intents for a specific customer', async () => {
 38 |     const mockPaymentIntents = [
 39 |       {
 40 |         id: 'pi_123456',
 41 |         customer: 'cus_123456',
 42 |         amount: 1000,
 43 |         status: 'succeeded',
 44 |         description: 'Test Payment Intent',
 45 |       },
 46 |     ];
 47 | 
 48 |     const context = {};
 49 | 
 50 |     stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
 51 | 
 52 |     const result = await listPaymentIntents(stripe, context, {
 53 |       customer: 'cus_123456',
 54 |     });
 55 | 
 56 |     expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
 57 |       {
 58 |         customer: 'cus_123456',
 59 |       },
 60 |       undefined
 61 |     );
 62 |     expect(result).toEqual(mockPaymentIntents);
 63 |   });
 64 | 
 65 |   it('should specify the connected account if included in context', async () => {
 66 |     const mockPaymentIntents = [
 67 |       {
 68 |         id: 'pi_123456',
 69 |         customer: 'cus_123456',
 70 |         amount: 1000,
 71 |         status: 'succeeded',
 72 |         description: 'Test Payment Intent',
 73 |       },
 74 |     ];
 75 | 
 76 |     const context = {
 77 |       account: 'acct_123456',
 78 |     };
 79 | 
 80 |     stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
 81 | 
 82 |     const result = await listPaymentIntents(stripe, context, {});
 83 | 
 84 |     expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
 85 |       {},
 86 |       {
 87 |         stripeAccount: context.account,
 88 |       }
 89 |     );
 90 |     expect(result).toEqual(mockPaymentIntents);
 91 |   });
 92 | 
 93 |   it('should list payment intents for a specific customer if included in context', async () => {
 94 |     const mockPaymentIntents = [
 95 |       {
 96 |         id: 'pi_123456',
 97 |         customer: 'cus_123456',
 98 |         amount: 1000,
 99 |         status: 'succeeded',
100 |         description: 'Test Payment Intent',
101 |       },
102 |     ];
103 | 
104 |     const context = {
105 |       customer: 'cus_123456',
106 |     };
107 | 
108 |     stripe.paymentIntents.list.mockResolvedValue({data: mockPaymentIntents});
109 | 
110 |     const result = await listPaymentIntents(stripe, context, {});
111 | 
112 |     expect(stripe.paymentIntents.list).toHaveBeenCalledWith(
113 |       {customer: context.customer},
114 |       undefined
115 |     );
116 |     expect(result).toEqual(mockPaymentIntents);
117 |   });
118 | });
119 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/coupons/createCoupon.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {Tool} from '@/shared/tools';
  5 | 
  6 | export const createCouponPrompt = (_context: Context = {}) => `
  7 | This tool will create a coupon in Stripe.
  8 | 
  9 | 
 10 | It takes several arguments:
 11 | - name (str): The name of the coupon.
 12 | 
 13 | Only use one of percent_off or amount_off, not both:
 14 | - percent_off (number, optional): The percentage discount to apply (between 0 and 100).
 15 | - amount_off (number, optional): The amount to subtract from an invoice (in cents).
 16 | 
 17 | Optional arguments for duration. Use if specific duration is desired, otherwise default to 'once'.
 18 | - duration (str, optional): How long the discount will last ('once', 'repeating', or 'forever'). Defaults to 'once'.
 19 | - duration_in_months (number, optional): The number of months the discount will last if duration is repeating.
 20 | `;
 21 | 
 22 | export const createCouponParameters = (
 23 |   _context: Context = {}
 24 | ): z.AnyZodObject =>
 25 |   z.object({
 26 |     name: z
 27 |       .string()
 28 |       .describe(
 29 |         'Name of the coupon displayed to customers on invoices or receipts'
 30 |       ),
 31 |     percent_off: z
 32 |       .number()
 33 |       .min(0)
 34 |       .max(100)
 35 |       .optional()
 36 |       .describe(
 37 |         'A positive float larger than 0, and smaller or equal to 100, that represents the discount the coupon will apply (required if amount_off is not passed)'
 38 |       ),
 39 |     amount_off: z
 40 |       .number()
 41 |       .describe(
 42 |         'A positive integer representing the amount to subtract from an invoice total (required if percent_off is not passed)'
 43 |       ),
 44 |     currency: z
 45 |       .string()
 46 |       .optional()
 47 |       .default('USD')
 48 |       .describe(
 49 |         'Three-letter ISO code for the currency of the amount_off parameter (required if amount_off is passed). Infer based on the amount_off. For example, if a coupon is $2 off, set currency to be USD.'
 50 |       ),
 51 |     duration: z
 52 |       .enum(['once', 'repeating', 'forever'])
 53 |       .default('once')
 54 |       .optional()
 55 |       .describe('How long the discount will last. Defaults to "once"'),
 56 |     duration_in_months: z
 57 |       .number()
 58 |       .optional()
 59 |       .describe(
 60 |         'The number of months the discount will last if duration is repeating'
 61 |       ),
 62 |   });
 63 | 
 64 | export const createCouponAnnotations = () => ({
 65 |   destructiveHint: false,
 66 |   idempotentHint: false,
 67 |   openWorldHint: true,
 68 |   readOnlyHint: false,
 69 |   title: 'Create coupon',
 70 | });
 71 | 
 72 | export const createCoupon = async (
 73 |   stripe: Stripe,
 74 |   context: Context,
 75 |   params: z.infer<ReturnType<typeof createCouponParameters>>
 76 | ) => {
 77 |   try {
 78 |     const coupon = await stripe.coupons.create(
 79 |       params,
 80 |       context.account ? {stripeAccount: context.account} : undefined
 81 |     );
 82 | 
 83 |     return {id: coupon.id};
 84 |   } catch (error: any) {
 85 |     return `Failed to create coupon: ${error.message}`;
 86 |   }
 87 | };
 88 | 
 89 | const tool = (context: Context): Tool => ({
 90 |   method: 'create_coupon',
 91 |   name: 'Create Coupon',
 92 |   description: createCouponPrompt(context),
 93 |   parameters: createCouponParameters(context),
 94 |   annotations: createCouponAnnotations(),
 95 |   actions: {
 96 |     coupons: {
 97 |       create: true,
 98 |     },
 99 |   },
100 |   execute: createCoupon,
101 | });
102 | 
103 | export default tool;
104 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/disputes/updateDispute.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {Tool} from '@/shared/tools';
  5 | 
  6 | export const updateDisputePrompt = (_context: Context = {}) => `
  7 | When you receive a dispute, contacting your customer is always the best first step. If that doesn't work, you can submit evidence to help resolve the dispute in your favor. This tool helps.
  8 | 
  9 | It takes the following arguments:
 10 | - dispute (string): The ID of the dispute to update
 11 | - evidence (object, optional): Evidence to upload for the dispute.
 12 |     - cancellation_policy_disclosure (string)
 13 |     - cancellation_rebuttal (string)
 14 |     - duplicate_charge_explanation (string)
 15 |     - uncategorized_text (string, optional): Any additional evidence or statements.
 16 | - submit (boolean, optional): Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.
 17 | `;
 18 | 
 19 | export const updateDisputeParameters = (_context: Context = {}) =>
 20 |   z.object({
 21 |     dispute: z.string().describe('The ID of the dispute to update'),
 22 |     evidence: z
 23 |       .object({
 24 |         cancellation_policy_disclosure: z
 25 |           .string()
 26 |           .max(20000)
 27 |           .optional()
 28 |           .describe(
 29 |             'An explanation of how and when the customer was shown your refund policy prior to purchase.'
 30 |           ),
 31 |         duplicate_charge_explanation: z
 32 |           .string()
 33 |           .max(20000)
 34 |           .optional()
 35 |           .describe(
 36 |             'An explanation of the difference between the disputed charge versus the prior charge that appears to be a duplicate.'
 37 |           ),
 38 |         uncategorized_text: z
 39 |           .string()
 40 |           .max(20000)
 41 |           .optional()
 42 |           .describe('Any additional evidence or statements.'),
 43 |       })
 44 |       .optional()
 45 |       .describe(
 46 |         'Evidence to upload, to respond to a dispute. Updating any field in the hash will submit all fields in the hash for review.'
 47 |       ),
 48 |     submit: z
 49 |       .boolean()
 50 |       .optional()
 51 |       .describe(
 52 |         'Whether to immediately submit evidence to the bank. If false, evidence is staged on the dispute.'
 53 |       ),
 54 |   });
 55 | 
 56 | export const updateDisputeAnnotations = () => ({
 57 |   destructiveHint: false,
 58 |   idempotentHint: false,
 59 |   openWorldHint: true,
 60 |   readOnlyHint: false,
 61 |   title: 'Update dispute',
 62 | });
 63 | 
 64 | export const updateDispute = async (
 65 |   stripe: Stripe,
 66 |   context: Context,
 67 |   params: z.infer<ReturnType<typeof updateDisputeParameters>>
 68 | ) => {
 69 |   try {
 70 |     const updateParams: Stripe.DisputeUpdateParams = {
 71 |       evidence: params.evidence,
 72 |       submit: params.submit,
 73 |     };
 74 | 
 75 |     const updatedDispute = await stripe.disputes.update(
 76 |       params.dispute,
 77 |       updateParams,
 78 |       context.account ? {stripeAccount: context.account} : undefined
 79 |     );
 80 | 
 81 |     return {id: updatedDispute.id};
 82 |   } catch (error) {
 83 |     return 'Failed to update dispute';
 84 |   }
 85 | };
 86 | 
 87 | const tool = (context: Context): Tool => ({
 88 |   method: 'update_dispute',
 89 |   name: 'Update Dispute',
 90 |   description: updateDisputePrompt(context),
 91 |   parameters: updateDisputeParameters(context),
 92 |   annotations: updateDisputeAnnotations(),
 93 |   actions: {
 94 |     disputes: {
 95 |       update: true,
 96 |     },
 97 |   },
 98 |   execute: updateDispute,
 99 | });
100 | 
101 | export default tool;
102 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/disputes/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {updateDispute} from '@/shared/disputes/updateDispute';
  2 | import {listDisputes} from '@/shared/disputes/listDisputes';
  3 | 
  4 | const Stripe = jest.fn().mockImplementation(() => ({
  5 |   disputes: {
  6 |     update: jest.fn(),
  7 |     list: jest.fn(),
  8 |   },
  9 | }));
 10 | 
 11 | let stripe: ReturnType<typeof Stripe>;
 12 | 
 13 | beforeEach(() => {
 14 |   stripe = new Stripe('fake-api-key');
 15 | });
 16 | 
 17 | describe('updateDispute', () => {
 18 |   it('should update a dispute and return the id', async () => {
 19 |     const params = {
 20 |       dispute: 'dp_123456',
 21 |       evidence: {
 22 |         uncategorized_text: 'Test product',
 23 |       },
 24 |       submit: true,
 25 |     };
 26 | 
 27 |     const context = {};
 28 | 
 29 |     const mockDispute = {id: 'dp_123456'};
 30 |     stripe.disputes.update.mockResolvedValue(mockDispute);
 31 | 
 32 |     const result = await updateDispute(stripe, context, params);
 33 | 
 34 |     expect(stripe.disputes.update).toHaveBeenCalledWith(
 35 |       params.dispute,
 36 |       {
 37 |         evidence: params.evidence,
 38 |         submit: params.submit,
 39 |       },
 40 |       undefined
 41 |     );
 42 |     expect(result).toEqual({id: mockDispute.id});
 43 |   });
 44 | 
 45 |   it('should specify the connected account if included in context', async () => {
 46 |     const params = {
 47 |       dispute: 'dp_123456',
 48 |       evidence: {
 49 |         uncategorized_text: 'Test product',
 50 |       },
 51 |       submit: true,
 52 |     };
 53 | 
 54 |     const context = {
 55 |       account: 'acct_123456',
 56 |     };
 57 | 
 58 |     const mockDispute = {id: 'dp_123456'};
 59 |     stripe.disputes.update.mockResolvedValue(mockDispute);
 60 | 
 61 |     const result = await updateDispute(stripe, context, params);
 62 | 
 63 |     expect(stripe.disputes.update).toHaveBeenCalledWith(
 64 |       params.dispute,
 65 |       {
 66 |         evidence: params.evidence,
 67 |         submit: params.submit,
 68 |       },
 69 |       {
 70 |         stripeAccount: context.account,
 71 |       }
 72 |     );
 73 |     expect(result).toEqual({id: mockDispute.id});
 74 |   });
 75 | });
 76 | 
 77 | describe('listDisputes', () => {
 78 |   it('should list disputes and return their ids', async () => {
 79 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
 80 | 
 81 |     const context = {};
 82 | 
 83 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
 84 |     const result = await listDisputes(stripe, context, {});
 85 | 
 86 |     expect(stripe.disputes.list).toHaveBeenCalledWith({}, undefined);
 87 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
 88 |   });
 89 | 
 90 |   it('should specify the connected account if included in context', async () => {
 91 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
 92 | 
 93 |     const context = {
 94 |       account: 'acct_123456',
 95 |     };
 96 | 
 97 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
 98 |     const result = await listDisputes(stripe, context, {});
 99 | 
100 |     expect(stripe.disputes.list).toHaveBeenCalledWith(
101 |       {},
102 |       {
103 |         stripeAccount: context.account,
104 |       }
105 |     );
106 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
107 |   });
108 | 
109 |   it('should pass through list parameters', async () => {
110 |     const params = {
111 |       charge: 'ch_123456',
112 |       payment_intent: 'pi_123456',
113 |       limit: 5,
114 |     };
115 | 
116 |     const mockDisputes = [{id: 'dp_123456'}, {id: 'dp_789012'}];
117 | 
118 |     const context = {};
119 | 
120 |     stripe.disputes.list.mockResolvedValue({data: mockDisputes});
121 |     const result = await listDisputes(stripe, context, params);
122 | 
123 |     expect(stripe.disputes.list).toHaveBeenCalledWith(params, undefined);
124 |     expect(result).toEqual(mockDisputes.map(({id}) => ({id})));
125 |   });
126 | });
127 | 
```

--------------------------------------------------------------------------------
/typescript/src/shared/subscriptions/updateSubscription.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import Stripe from 'stripe';
  2 | import {z} from 'zod';
  3 | import type {Context} from '@/shared/configuration';
  4 | import type {Tool} from '@/shared/tools';
  5 | 
  6 | export const updateSubscriptionPrompt = (_context: Context = {}): string => {
  7 |   return `This tool will update an existing subscription in Stripe. If changing an existing subscription item, the existing subscription item has to be set to deleted and the new one has to be added.
  8 |   
  9 |   It takes the following arguments:
 10 |   - subscription (str, required): The ID of the subscription to update.
 11 |   - proration_behavior (str, optional): Determines how to handle prorations when the subscription items change. Options: 'create_prorations', 'none', 'always_invoice', 'none_implicit'.
 12 |   - items (array, optional): A list of subscription items to update, add, or remove. Each item can have the following properties:
 13 |     - id (str, optional): The ID of the subscription item to modify.
 14 |     - price (str, optional): The ID of the price to switch to.
 15 |     - quantity (int, optional): The quantity of the plan to subscribe to.
 16 |     - deleted (bool, optional): Whether to delete this item.
 17 |   `;
 18 | };
 19 | 
 20 | export const updateSubscription = async (
 21 |   stripe: Stripe,
 22 |   context: Context,
 23 |   params: z.infer<ReturnType<typeof updateSubscriptionParameters>>
 24 | ) => {
 25 |   try {
 26 |     const {subscription: subscriptionId, ...updateParams} = params;
 27 | 
 28 |     const subscription = await stripe.subscriptions.update(
 29 |       subscriptionId,
 30 |       updateParams,
 31 |       context.account ? {stripeAccount: context.account} : undefined
 32 |     );
 33 | 
 34 |     return subscription;
 35 |   } catch (error) {
 36 |     return 'Failed to update subscription';
 37 |   }
 38 | };
 39 | 
 40 | export const updateSubscriptionParameters = (
 41 |   _context: Context = {}
 42 | ): z.AnyZodObject => {
 43 |   return z.object({
 44 |     subscription: z.string().describe('The ID of the subscription to update.'),
 45 |     proration_behavior: z
 46 |       .enum(['create_prorations', 'none', 'always_invoice', 'none_implicit'])
 47 |       .optional()
 48 |       .describe(
 49 |         'Determines how to handle prorations when the subscription items change.'
 50 |       ),
 51 |     items: z
 52 |       .array(
 53 |         z.object({
 54 |           id: z
 55 |             .string()
 56 |             .optional()
 57 |             .describe('The ID of the subscription item to modify.'),
 58 |           price: z
 59 |             .string()
 60 |             .optional()
 61 |             .describe('The ID of the price to switch to.'),
 62 |           quantity: z
 63 |             .number()
 64 |             .int()
 65 |             .min(1)
 66 |             .optional()
 67 |             .describe('The quantity of the plan to subscribe to.'),
 68 |           deleted: z
 69 |             .boolean()
 70 |             .optional()
 71 |             .describe('Whether to delete this item.'),
 72 |         })
 73 |       )
 74 |       .optional()
 75 |       .describe('A list of subscription items to update, add, or remove.'),
 76 |   });
 77 | };
 78 | 
 79 | export const updateSubscriptionAnnotations = () => ({
 80 |   destructiveHint: false,
 81 |   idempotentHint: false,
 82 |   openWorldHint: true,
 83 |   readOnlyHint: false,
 84 |   title: 'Update subscription',
 85 | });
 86 | 
 87 | const tool = (context: Context): Tool => ({
 88 |   method: 'update_subscription',
 89 |   name: 'Update Subscription',
 90 |   description: updateSubscriptionPrompt(context),
 91 |   parameters: updateSubscriptionParameters(context),
 92 |   annotations: updateSubscriptionAnnotations(),
 93 |   actions: {
 94 |     subscriptions: {
 95 |       update: true,
 96 |     },
 97 |   },
 98 |   execute: updateSubscription,
 99 | });
100 | 
101 | export default tool;
102 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/prompts.py:
--------------------------------------------------------------------------------

```python
  1 | CREATE_CUSTOMER_PROMPT = """
  2 | This tool will create a customer in Stripe.
  3 | 
  4 | It takes two arguments:
  5 | - name (str): The name of the customer.
  6 | - email (str, optional): The email of the customer.
  7 | """
  8 | 
  9 | LIST_CUSTOMERS_PROMPT = """
 10 | This tool will fetch a list of Customers from Stripe.
 11 | 
 12 | It takes no input.
 13 | """
 14 | 
 15 | CREATE_PRODUCT_PROMPT = """
 16 | This tool will create a product in Stripe.
 17 | 
 18 | It takes two arguments:
 19 | - name (str): The name of the product.
 20 | - description (str, optional): The description of the product.
 21 | """
 22 | 
 23 | LIST_PRODUCTS_PROMPT = """
 24 | This tool will fetch a list of Products from Stripe.
 25 | 
 26 | It takes one optional argument:
 27 | - limit (int, optional): The number of products to return.
 28 | """
 29 | 
 30 | CREATE_PRICE_PROMPT = """
 31 | This tool will create a price in Stripe. If a product has not already been
 32 | specified, a product should be created first.
 33 | 
 34 | It takes three arguments:
 35 | - product (str): The ID of the product to create the price for.
 36 | - unit_amount (int): The unit amount of the price in cents.
 37 | - currency (str): The currency of the price.
 38 | """
 39 | 
 40 | LIST_PRICES_PROMPT = """
 41 | This tool will fetch a list of Prices from Stripe.
 42 | 
 43 | It takes two arguments:
 44 | - product (str, optional): The ID of the product to list prices for.
 45 | - limit (int, optional): The number of prices to return.
 46 | """
 47 | 
 48 | CREATE_PAYMENT_LINK_PROMPT = """
 49 | This tool will create a payment link in Stripe.
 50 | 
 51 | It takes two arguments:
 52 | - price (str): The ID of the price to create the payment link for.
 53 | - quantity (int): The quantity of the product to include in the payment link.
 54 | - redirect_url (string, optional): The URL the customer will be redirected to after the purchase is complete.
 55 | """
 56 | 
 57 | LIST_INVOICES_PROMPT = """
 58 | This tool will list invoices in Stripe.
 59 | 
 60 | It takes two arguments:
 61 | - customer (str, optional): The ID of the customer to list the invoices for.
 62 | - limit (int, optional): The number of prices to return.
 63 | """
 64 | 
 65 | CREATE_INVOICE_PROMPT = """
 66 | This tool will create an invoice in Stripe.
 67 | 
 68 | It takes one argument:
 69 | - customer (str): The ID of the customer to create the invoice for.
 70 | """
 71 | 
 72 | CREATE_INVOICE_ITEM_PROMPT = """
 73 | This tool will create an invoice item in Stripe.
 74 | 
 75 | It takes two arguments:
 76 | - customer (str): The ID of the customer to create the invoice item for.
 77 | - price (str): The ID of the price to create the invoice item for.
 78 | - invoice (str): The ID of the invoice to create the invoice item for.
 79 | """
 80 | 
 81 | FINALIZE_INVOICE_PROMPT = """
 82 | This tool will finalize an invoice in Stripe.
 83 | 
 84 | It takes one argument:
 85 | - invoice (str): The ID of the invoice to finalize.
 86 | """
 87 | 
 88 | RETRIEVE_BALANCE_PROMPT = """
 89 | This tool will retrieve the balance from Stripe. It takes no input.
 90 | """
 91 | 
 92 | CREATE_REFUND_PROMPT = """
 93 | This tool will refund a payment intent in Stripe.
 94 | 
 95 | It takes three arguments:
 96 | - payment_intent (str): The ID of the payment intent to refund.
 97 | - amount (int, optional): The amount to refund in cents.
 98 | - reason (str, optional): The reason for the refund.
 99 | """
100 | 
101 | LIST_PAYMENT_INTENTS_PROMPT = """
102 | This tool will list payment intents in Stripe.
103 | 
104 | It takes two arguments:
105 | - customer (str, optional): The ID of the customer to list payment intents for.
106 | - limit (int, optional): The number of payment intents to return.
107 | """
108 | 
109 | CREATE_BILLING_PORTAL_SESSION_PROMPT = """
110 | This tool will create a billing portal session.
111 | 
112 | It takes two arguments:
113 | - customer (str): The ID of the customer to create the invoice item for.
114 | - return_url (str, optional): The default URL to return to afterwards.
115 | """
116 | 
```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
  2 | import {z} from 'zod';
  3 | import {
  4 |   PaymentState,
  5 |   experimental_PaidMcpAgent as PaidMcpAgent,
  6 | } from '@stripe/agent-toolkit/cloudflare';
  7 | import {generateImage} from './imageGenerator';
  8 | import {OAuthProvider} from '@cloudflare/workers-oauth-provider';
  9 | import app from './app';
 10 | 
 11 | type Bindings = Env;
 12 | 
 13 | type Props = {
 14 |   userEmail: string;
 15 | };
 16 | 
 17 | type State = PaymentState & {};
 18 | 
 19 | export class MyMCP extends PaidMcpAgent<Bindings, State, Props> {
 20 |   server = new McpServer({
 21 |     name: 'Demo',
 22 |     version: '1.0.0',
 23 |   });
 24 | 
 25 |   initialState: State = {};
 26 | 
 27 |   async init() {
 28 |     this.server.tool('add', {a: z.number(), b: z.number()}, ({a, b}) => {
 29 |       return {
 30 |         content: [{type: 'text', text: `Result: ${a + b}`}],
 31 |       };
 32 |     });
 33 | 
 34 |     // One-time payment, then the tool is usable forever
 35 |     this.paidTool(
 36 |       'buy_premium',
 37 |       'Buy a premium account',
 38 |       {},
 39 |       () => {
 40 |         return {
 41 |           content: [{type: 'text', text: `You now have a premium account!`}],
 42 |         };
 43 |       },
 44 |       {
 45 |         checkout: {
 46 |           success_url: 'http://localhost:4242/payment/success',
 47 |           line_items: [
 48 |             {
 49 |               price: process.env.STRIPE_PRICE_ID_ONE_TIME_PAYMENT,
 50 |               quantity: 1,
 51 |             },
 52 |           ],
 53 |           mode: 'payment',
 54 |         },
 55 |         paymentReason:
 56 |           'Open the checkout link in the browser to buy a premium account.',
 57 |       }
 58 |     );
 59 | 
 60 |     // Subscription, then the tool is usable as long as the subscription is active
 61 |     this.paidTool(
 62 |       'big_add',
 63 |       'Add two numbers together',
 64 |       {
 65 |         a: z.number(),
 66 |         b: z.number(),
 67 |       },
 68 |       ({a, b}) => {
 69 |         return {
 70 |           content: [{type: 'text', text: `Result: ${a + b}`}],
 71 |         };
 72 |       },
 73 |       {
 74 |         checkout: {
 75 |           success_url: 'http://localhost:4242/payment/success',
 76 |           line_items: [
 77 |             {
 78 |               price: process.env.STRIPE_PRICE_ID_SUBSCRIPTION,
 79 |               quantity: 1,
 80 |             },
 81 |           ],
 82 |           mode: 'subscription',
 83 |         },
 84 |         paymentReason:
 85 |           'You must pay a subscription to add two big numbers together.',
 86 |       }
 87 |     );
 88 | 
 89 |     // Usage-based metered payments (Each tool call requires a payment)
 90 |     this.paidTool(
 91 |       'generate_emoji',
 92 |       'Generate an emoji given a single word (the `object` parameter describing the emoji)',
 93 |       {
 94 |         object: z.string().describe('one word'),
 95 |       },
 96 |       ({object}) => {
 97 |         return {
 98 |           content: [{type: 'text', text: generateImage(object)}],
 99 |         };
100 |       },
101 |       {
102 |         checkout: {
103 |           success_url: 'http://localhost:4242/payment/success',
104 |           line_items: [
105 |             {
106 |               price: process.env.STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION,
107 |             },
108 |           ],
109 |           mode: 'subscription',
110 |         },
111 |         meterEvent: 'image_generation',
112 |         paymentReason:
113 |           'You get 3 free generations, then we charge 10 cents per generation.',
114 |       }
115 |     );
116 |   }
117 | }
118 | 
119 | // Export the OAuth handler as the default
120 | export default new OAuthProvider({
121 |   apiRoute: '/sse',
122 |   apiHandlers: {
123 |     // @ts-ignore
124 |     '/sse': MyMCP.serveSSE('/sse'),
125 |     // @ts-ignore
126 |     '/mcp': MyMCP.serve('/mcp'),
127 |   },
128 |   // @ts-ignore
129 |   defaultHandler: app,
130 |   authorizeEndpoint: '/authorize',
131 |   tokenEndpoint: '/token',
132 |   clientRegistrationEndpoint: '/register',
133 | });
134 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/strands/hooks.py:
--------------------------------------------------------------------------------

```python
 1 | from typing import Any, Optional, Dict
 2 | from ..api import StripeAPI
 3 | 
 4 | 
 5 | class BillingHooks:
 6 |     """Billing hooks for Strands framework to track usage and create meter events."""
 7 | 
 8 |     def __init__(
 9 |         self,
10 |         stripe: StripeAPI,
11 |         type: str,
12 |         customer: str,
13 |         meter: Optional[str] = None,
14 |         meters: Optional[Dict[str, str]] = None
15 |     ):
16 |         """
17 |         Initialize billing hooks.
18 | 
19 |         Args:
20 |             stripe: StripeAPI instance
21 |             type: Type of billing - "outcome" or "token"
22 |             customer: Customer ID for billing
23 |             meter: Single meter ID for outcome-based billing
24 |             meters: Dictionary of meter IDs for token-based billing (input/output)
25 |         """
26 |         self.type = type
27 |         self.stripe = stripe
28 |         self.customer = customer
29 |         self.meter = meter
30 |         self.meters = meters or {}
31 | 
32 |     def on_start(self, context: Any = None) -> None:
33 |         """Called when agent execution starts."""
34 |         pass
35 | 
36 |     def on_end(self, context: Any = None, output: Any = None, usage: Any = None) -> None:
37 |         """
38 |         Called when agent execution ends.
39 | 
40 |         Args:
41 |             context: Execution context (may contain usage information)
42 |             output: Agent output
43 |             usage: Usage information (tokens, etc.)
44 |         """
45 |         if self.type == "outcome":
46 |             # Create a single meter event for outcome-based billing
47 |             if self.meter:
48 |                 self.stripe.create_meter_event(self.meter, self.customer)
49 | 
50 |         elif self.type == "token":
51 |             # Create meter events for token-based billing
52 |             if usage:
53 |                 # Try to extract token usage from different possible formats
54 |                 input_tokens = self._extract_input_tokens(usage, context)
55 |                 output_tokens = self._extract_output_tokens(usage, context)
56 | 
57 |                 if input_tokens and self.meters.get("input"):
58 |                     self.stripe.create_meter_event(
59 |                         self.meters["input"],
60 |                         self.customer,
61 |                         str(input_tokens)
62 |                     )
63 | 
64 |                 if output_tokens and self.meters.get("output"):
65 |                     self.stripe.create_meter_event(
66 |                         self.meters["output"],
67 |                         self.customer,
68 |                         str(output_tokens)
69 |                     )
70 | 
71 |     def on_error(self, context: Any = None, error: Exception = None) -> None:
72 |         """Called when agent execution encounters an error."""
73 |         pass
74 | 
75 |     def _extract_input_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
76 |         """Extract input token count from usage information."""
77 |         if hasattr(usage, 'input_tokens'):
78 |             return usage.input_tokens
79 |         elif isinstance(usage, dict):
80 |             return usage.get('input_tokens') or usage.get('prompt_tokens')
81 |         elif context and hasattr(context, 'usage') and hasattr(context.usage, 'input_tokens'):
82 |             return context.usage.input_tokens
83 |         return None
84 | 
85 |     def _extract_output_tokens(self, usage: Any, context: Any = None) -> Optional[int]:
86 |         """Extract output token count from usage information."""
87 |         if hasattr(usage, 'output_tokens'):
88 |             return usage.output_tokens
89 |         elif isinstance(usage, dict):
90 |             return usage.get('output_tokens') or usage.get('completion_tokens')
91 |         elif context and hasattr(context, 'usage') and hasattr(context.usage, 'output_tokens'):
92 |             return context.usage.output_tokens
93 |         return None
94 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/api.py:
--------------------------------------------------------------------------------

```python
  1 | """Util that calls Stripe."""
  2 | 
  3 | from __future__ import annotations
  4 | 
  5 | import json
  6 | import stripe
  7 | from typing import Optional
  8 | from pydantic import BaseModel
  9 | 
 10 | from .configuration import Context
 11 | 
 12 | from .functions import (
 13 |     create_customer,
 14 |     list_customers,
 15 |     create_product,
 16 |     list_products,
 17 |     create_price,
 18 |     list_prices,
 19 |     create_payment_link,
 20 |     list_invoices,
 21 |     create_invoice,
 22 |     create_invoice_item,
 23 |     finalize_invoice,
 24 |     retrieve_balance,
 25 |     create_refund,
 26 |     list_payment_intents,
 27 |     create_billing_portal_session,
 28 | )
 29 | 
 30 | 
 31 | class StripeAPI(BaseModel):
 32 |     """ "Wrapper for Stripe API"""
 33 | 
 34 |     _context: Context
 35 | 
 36 |     def __init__(self, secret_key: str, context: Optional[Context]):
 37 |         super().__init__()
 38 | 
 39 |         self._context = context if context is not None else Context()
 40 | 
 41 |         stripe.api_key = secret_key
 42 |         stripe.set_app_info(
 43 |             "stripe-agent-toolkit-python",
 44 |             version="0.6.1",
 45 |             url="https://github.com/stripe/agent-toolkit",
 46 |         )
 47 | 
 48 |     def create_meter_event(self, event: str, customer: str, value: Optional[str] = None) -> str:
 49 |         meter_event_data: dict = {
 50 |             "event_name": event,
 51 |             "payload": {
 52 |                 "stripe_customer_id": customer,
 53 |             },
 54 |         }
 55 |         if value is not None:
 56 |             meter_event_data["payload"]["value"] = value
 57 | 
 58 |         if self._context.get("account") is not None:
 59 |             account = self._context.get("account")
 60 |             if account is not None:
 61 |                 meter_event_data["stripe_account"] = account
 62 | 
 63 |         stripe.billing.MeterEvent.create(**meter_event_data)
 64 | 
 65 |     def run(self, method: str, *args, **kwargs) -> str:
 66 |         if method == "create_customer":
 67 |             return json.dumps(create_customer(self._context, *args, **kwargs))
 68 |         elif method == "list_customers":
 69 |             return json.dumps(list_customers(self._context, *args, **kwargs))
 70 |         elif method == "create_product":
 71 |             return json.dumps(create_product(self._context, *args, **kwargs))
 72 |         elif method == "list_products":
 73 |             return json.dumps(list_products(self._context, *args, **kwargs))
 74 |         elif method == "create_price":
 75 |             return json.dumps(create_price(self._context, *args, **kwargs))
 76 |         elif method == "list_prices":
 77 |             return json.dumps(list_prices(self._context, *args, **kwargs))
 78 |         elif method == "create_payment_link":
 79 |             return json.dumps(
 80 |                 create_payment_link(self._context, *args, **kwargs)
 81 |             )
 82 |         elif method == "list_invoices":
 83 |             return json.dumps(list_invoices(self._context, *args, **kwargs))
 84 |         elif method == "create_invoice":
 85 |             return json.dumps(create_invoice(self._context, *args, **kwargs))
 86 |         elif method == "create_invoice_item":
 87 |             return json.dumps(
 88 |                 create_invoice_item(self._context, *args, **kwargs)
 89 |             )
 90 |         elif method == "finalize_invoice":
 91 |             return json.dumps(finalize_invoice(self._context, *args, **kwargs))
 92 |         elif method == "retrieve_balance":
 93 |             return json.dumps(retrieve_balance(self._context, *args, **kwargs))
 94 |         elif method == "create_refund":
 95 |             return json.dumps(create_refund(self._context, *args, **kwargs))
 96 |         elif method == "list_payment_intents":
 97 |             return json.dumps(
 98 |                 list_payment_intents(self._context, *args, **kwargs)
 99 |             )
100 |         elif method == "create_billing_portal_session":
101 |             return json.dumps(
102 |                 create_billing_portal_session(self._context, *args, **kwargs)
103 |             )
104 |         else:
105 |             raise ValueError("Invalid method " + method)
106 | 
```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/src/app.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
  2 | 
  3 | import {Hono} from 'hono';
  4 | import {
  5 |   layout,
  6 |   homeContent,
  7 |   parseApproveFormBody,
  8 |   renderAuthorizationRejectedContent,
  9 |   renderAuthorizationApprovedContent,
 10 |   renderLoggedInAuthorizeScreen,
 11 |   renderLoggedOutAuthorizeScreen,
 12 |   renderPaymentSuccessContent,
 13 | } from './utils';
 14 | import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
 15 | 
 16 | export type Bindings = Env & {
 17 |   OAUTH_PROVIDER: OAuthHelpers;
 18 | };
 19 | 
 20 | const app = new Hono<{
 21 |   Bindings: Bindings;
 22 | }>();
 23 | 
 24 | // Render a basic homepage placeholder to make sure the app is up
 25 | app.get('/', async (c) => {
 26 |   const content = await homeContent(c.req.raw);
 27 |   return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
 28 | });
 29 | 
 30 | // Render an authorization page
 31 | // If the user is logged in, we'll show a form to approve the appropriate scopes
 32 | // If the user is not logged in, we'll show a form to both login and approve the scopes
 33 | app.get('/authorize', async (c) => {
 34 |   // We don't have an actual auth system, so to demonstrate both paths, you can
 35 |   // hard-code whether the user is logged in or not. We'll default to true
 36 |   // const isLoggedIn = false;
 37 |   const isLoggedIn = true;
 38 | 
 39 |   const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
 40 | 
 41 |   const oauthScopes = [
 42 |     {
 43 |       name: 'read_profile',
 44 |       description: 'Read your basic profile information',
 45 |     },
 46 |     {name: 'read_data', description: 'Access your stored data'},
 47 |     {name: 'write_data', description: 'Create and modify your data'},
 48 |   ];
 49 | 
 50 |   if (isLoggedIn) {
 51 |     const content = await renderLoggedInAuthorizeScreen(
 52 |       oauthScopes,
 53 |       oauthReqInfo
 54 |     );
 55 |     return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 56 |   }
 57 | 
 58 |   const content = await renderLoggedOutAuthorizeScreen(
 59 |     oauthScopes,
 60 |     oauthReqInfo
 61 |   );
 62 |   return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 63 | });
 64 | 
 65 | app.get('/payment/success', async (c) => {
 66 |   return c.html(
 67 |     layout(
 68 |       await renderPaymentSuccessContent(),
 69 |       'MCP Remote Auth Demo - Payment Success'
 70 |     )
 71 |   );
 72 | });
 73 | 
 74 | // The /authorize page has a form that will POST to /approve
 75 | // This endpoint is responsible for validating any login information and
 76 | // then completing the authorization request with the OAUTH_PROVIDER
 77 | app.post('/approve', async (c) => {
 78 |   const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
 79 |     await c.req.parseBody()
 80 |   );
 81 | 
 82 |   if (!oauthReqInfo) {
 83 |     return c.html('INVALID LOGIN', 401);
 84 |   }
 85 | 
 86 |   // If the user needs to both login and approve, we should validate the login first
 87 |   if (action === 'login_approve') {
 88 |     // We'll allow any values for email and password for this demo
 89 |     // but you could validate them here
 90 |     // Ex:
 91 |     // if (email !== "[email protected]" || password !== "password") {
 92 |     // biome-ignore lint/correctness/noConstantCondition: This is a demo
 93 |     if (false) {
 94 |       return c.html(
 95 |         layout(
 96 |           await renderAuthorizationRejectedContent('/'),
 97 |           'MCP Remote Auth Demo - Authorization Status'
 98 |         )
 99 |       );
100 |     }
101 |   }
102 | 
103 |   // The user must be successfully logged in and have approved the scopes, so we
104 |   // can complete the authorization request
105 |   const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
106 |     request: oauthReqInfo,
107 |     userId: email,
108 |     metadata: {
109 |       label: 'Test User',
110 |     },
111 |     scope: oauthReqInfo.scope,
112 |     props: {
113 |       // Here, you can send data to the MCP server
114 |       userEmail: email,
115 |     },
116 |   });
117 | 
118 |   // Store the redirect URL per email in KV somewhere
119 |   c.env.OAUTH_KV.put(email, redirectTo);
120 | 
121 |   return c.html(
122 |     layout(
123 |       await renderAuthorizationApprovedContent(redirectTo),
124 |       'MCP Remote Auth Demo - Authorization Status'
125 |     )
126 |   );
127 | });
128 | 
129 | export default app;
130 | 
```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/src/oauth.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/app.ts
  2 | 
  3 | import {Hono} from 'hono';
  4 | import {
  5 |   layout,
  6 |   homeContent,
  7 |   parseApproveFormBody,
  8 |   renderAuthorizationRejectedContent,
  9 |   renderAuthorizationApprovedContent,
 10 |   renderLoggedInAuthorizeScreen,
 11 |   renderLoggedOutAuthorizeScreen,
 12 |   renderPaymentSuccessContent,
 13 | } from './utils';
 14 | import type {OAuthHelpers} from '@cloudflare/workers-oauth-provider';
 15 | 
 16 | export type Bindings = Env & {
 17 |   OAUTH_PROVIDER: OAuthHelpers;
 18 | };
 19 | 
 20 | const app = new Hono<{
 21 |   Bindings: Bindings;
 22 | }>();
 23 | 
 24 | // Render a basic homepage placeholder to make sure the app is up
 25 | app.get('/', async (c) => {
 26 |   const content = await homeContent(c.req.raw);
 27 |   return c.html(layout(content, 'MCP Remote Auth Demo - Home'));
 28 | });
 29 | 
 30 | // Render an authorization page
 31 | // If the user is logged in, we'll show a form to approve the appropriate scopes
 32 | // If the user is not logged in, we'll show a form to both login and approve the scopes
 33 | app.get('/authorize', async (c) => {
 34 |   // We don't have an actual auth system, so to demonstrate both paths, you can
 35 |   // hard-code whether the user is logged in or not. We'll default to true
 36 |   // const isLoggedIn = false;
 37 |   const isLoggedIn = true;
 38 | 
 39 |   const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
 40 | 
 41 |   const oauthScopes = [
 42 |     {
 43 |       name: 'read_profile',
 44 |       description: 'Read your basic profile information',
 45 |     },
 46 |     {name: 'read_data', description: 'Access your stored data'},
 47 |     {name: 'write_data', description: 'Create and modify your data'},
 48 |   ];
 49 | 
 50 |   if (isLoggedIn) {
 51 |     const content = await renderLoggedInAuthorizeScreen(
 52 |       oauthScopes,
 53 |       oauthReqInfo
 54 |     );
 55 |     return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 56 |   }
 57 | 
 58 |   const content = await renderLoggedOutAuthorizeScreen(
 59 |     oauthScopes,
 60 |     oauthReqInfo
 61 |   );
 62 |   return c.html(layout(content, 'MCP Remote Auth Demo - Authorization'));
 63 | });
 64 | 
 65 | app.get('/payment/success', async (c) => {
 66 |   return c.html(
 67 |     layout(
 68 |       await renderPaymentSuccessContent(),
 69 |       'MCP Remote Auth Demo - Payment Success'
 70 |     )
 71 |   );
 72 | });
 73 | 
 74 | // The /authorize page has a form that will POST to /approve
 75 | // This endpoint is responsible for validating any login information and
 76 | // then completing the authorization request with the OAUTH_PROVIDER
 77 | app.post('/approve', async (c) => {
 78 |   const {action, oauthReqInfo, email, password} = await parseApproveFormBody(
 79 |     await c.req.parseBody()
 80 |   );
 81 | 
 82 |   if (!oauthReqInfo) {
 83 |     return c.html('INVALID LOGIN', 401);
 84 |   }
 85 | 
 86 |   // If the user needs to both login and approve, we should validate the login first
 87 |   if (action === 'login_approve') {
 88 |     // We'll allow any values for email and password for this demo
 89 |     // but you could validate them here
 90 |     // Ex:
 91 |     // if (email !== "[email protected]" || password !== "password") {
 92 |     // biome-ignore lint/correctness/noConstantCondition: This is a demo
 93 |     if (false) {
 94 |       return c.html(
 95 |         layout(
 96 |           await renderAuthorizationRejectedContent('/'),
 97 |           'MCP Remote Auth Demo - Authorization Status'
 98 |         )
 99 |       );
100 |     }
101 |   }
102 | 
103 |   // The user must be successfully logged in and have approved the scopes, so we
104 |   // can complete the authorization request
105 |   const {redirectTo} = await c.env.OAUTH_PROVIDER.completeAuthorization({
106 |     request: oauthReqInfo,
107 |     userId: email,
108 |     metadata: {
109 |       label: 'Test User',
110 |     },
111 |     scope: oauthReqInfo.scope,
112 |     props: {
113 |       // Here, you can send data to the MCP server
114 |       userEmail: email,
115 |     },
116 |   });
117 | 
118 |   // Store the redirect URL per email in KV somewhere
119 |   c.env.OAUTH_KV.put(email, redirectTo);
120 | 
121 |   return c.html(
122 |     layout(
123 |       await renderAuthorizationApprovedContent(redirectTo),
124 |       'MCP Remote Auth Demo - Authorization Status'
125 |     )
126 |   );
127 | });
128 | 
129 | export default app;
130 | 
```

--------------------------------------------------------------------------------
/modelcontextprotocol/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
  4 | import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | import {green, red, yellow} from 'colors';
  6 | 
  7 | type ToolkitConfig = {
  8 |   actions: {
  9 |     [product: string]: {[action: string]: boolean};
 10 |   };
 11 |   context?: {
 12 |     account?: string;
 13 |     mode: 'modelcontextprotocol';
 14 |   };
 15 | };
 16 | 
 17 | type Options = {
 18 |   tools?: string[];
 19 |   apiKey?: string;
 20 |   stripeAccount?: string;
 21 | };
 22 | 
 23 | const ACCEPTED_ARGS = ['api-key', 'tools', 'stripe-account'];
 24 | const ACCEPTED_TOOLS = [
 25 |   'coupons.create',
 26 |   'coupons.read',
 27 |   'customers.create',
 28 |   'customers.read',
 29 |   'products.create',
 30 |   'products.read',
 31 |   'prices.create',
 32 |   'prices.read',
 33 |   'paymentLinks.create',
 34 |   'invoices.create',
 35 |   'invoices.read',
 36 |   'invoices.update',
 37 |   'invoiceItems.create',
 38 |   'balance.read',
 39 |   'refunds.create',
 40 |   'paymentIntents.read',
 41 |   'subscriptions.read',
 42 |   'subscriptions.update',
 43 |   'disputes.read',
 44 |   'disputes.update',
 45 |   'documentation.read',
 46 | ];
 47 | 
 48 | export function parseArgs(args: string[]): Options {
 49 |   const options: Options = {};
 50 | 
 51 |   args.forEach((arg) => {
 52 |     if (arg.startsWith('--')) {
 53 |       const [key, value] = arg.slice(2).split('=');
 54 | 
 55 |       if (key == 'tools') {
 56 |         options.tools = value.split(',');
 57 |       } else if (key == 'api-key') {
 58 |         if (!value.startsWith('sk_') && !value.startsWith('rk_')) {
 59 |           throw new Error('API key must start with "sk_" or "rk_".');
 60 |         }
 61 |         options.apiKey = value;
 62 |       } else if (key == 'stripe-account') {
 63 |         // Validate api-key format
 64 |         if (!value.startsWith('acct_')) {
 65 |           throw new Error('Stripe account must start with "acct_".');
 66 |         }
 67 |         options.stripeAccount = value;
 68 |       } else {
 69 |         throw new Error(
 70 |           `Invalid argument: ${key}. Accepted arguments are: ${ACCEPTED_ARGS.join(
 71 |             ', '
 72 |           )}`
 73 |         );
 74 |       }
 75 |     }
 76 |   });
 77 | 
 78 |   // Check if required tools arguments is present
 79 |   if (!options.tools) {
 80 |     throw new Error('The --tools arguments must be provided.');
 81 |   }
 82 | 
 83 |   // Validate tools against accepted enum values
 84 |   options.tools.forEach((tool: string) => {
 85 |     if (tool == 'all') {
 86 |       return;
 87 |     }
 88 |     if (!ACCEPTED_TOOLS.includes(tool.trim())) {
 89 |       throw new Error(
 90 |         `Invalid tool: ${tool}. Accepted tools are: ${ACCEPTED_TOOLS.join(
 91 |           ', '
 92 |         )}`
 93 |       );
 94 |     }
 95 |   });
 96 | 
 97 |   // Check if API key is either provided in args or set in environment variables
 98 |   const apiKey = options.apiKey || process.env.STRIPE_SECRET_KEY;
 99 |   if (!apiKey) {
100 |     throw new Error(
101 |       'Stripe API key not provided. Please either pass it as an argument --api-key=$KEY or set the STRIPE_SECRET_KEY environment variable.'
102 |     );
103 |   }
104 |   options.apiKey = apiKey;
105 | 
106 |   return options;
107 | }
108 | 
109 | function handleError(error: any) {
110 |   console.error(red('\n🚨  Error initializing Stripe MCP server:\n'));
111 |   console.error(yellow(`   ${error.message}\n`));
112 | }
113 | 
114 | export async function main() {
115 |   const options = parseArgs(process.argv.slice(2));
116 | 
117 |   // Create the StripeAgentToolkit instance
118 |   const selectedTools = options.tools!;
119 |   const configuration: ToolkitConfig = {actions: {}};
120 | 
121 |   if (selectedTools.includes('all')) {
122 |     ACCEPTED_TOOLS.forEach((tool) => {
123 |       const [product, action] = tool.split('.');
124 |       configuration.actions[product] = {
125 |         ...configuration.actions[product],
126 |         [action]: true,
127 |       };
128 |     });
129 |   } else {
130 |     selectedTools.forEach((tool: any) => {
131 |       const [product, action] = tool.split('.');
132 |       configuration.actions[product] = {[action]: true};
133 |     });
134 |   }
135 | 
136 |   configuration.context = {
137 |     mode: 'modelcontextprotocol',
138 |   };
139 | 
140 |   // Append stripe account to configuration if provided
141 |   if (options.stripeAccount) {
142 |     configuration.context.account = options.stripeAccount;
143 |   }
144 | 
145 |   const server = new StripeAgentToolkit({
146 |     secretKey: options.apiKey!,
147 |     configuration: configuration,
148 |   });
149 | 
150 |   const transport = new StdioServerTransport();
151 |   await server.connect(transport);
152 |   // We use console.error instead of console.log since console.log will output to stdio, which will confuse the MCP server
153 |   console.error(green('✅ Stripe MCP Server running on stdio'));
154 | }
155 | 
156 | if (require.main === module) {
157 |   main().catch((error) => {
158 |     handleError(error);
159 |   });
160 | }
161 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/tools.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Dict, List
  2 | 
  3 | from .prompts import (
  4 |     CREATE_CUSTOMER_PROMPT,
  5 |     LIST_CUSTOMERS_PROMPT,
  6 |     CREATE_PRODUCT_PROMPT,
  7 |     LIST_PRODUCTS_PROMPT,
  8 |     CREATE_PRICE_PROMPT,
  9 |     LIST_PRICES_PROMPT,
 10 |     CREATE_PAYMENT_LINK_PROMPT,
 11 |     LIST_INVOICES_PROMPT,
 12 |     CREATE_INVOICE_PROMPT,
 13 |     CREATE_INVOICE_ITEM_PROMPT,
 14 |     FINALIZE_INVOICE_PROMPT,
 15 |     RETRIEVE_BALANCE_PROMPT,
 16 |     CREATE_REFUND_PROMPT,
 17 |     LIST_PAYMENT_INTENTS_PROMPT,
 18 |     CREATE_BILLING_PORTAL_SESSION_PROMPT,
 19 | )
 20 | 
 21 | from .schema import (
 22 |     CreateCustomer,
 23 |     ListCustomers,
 24 |     CreateProduct,
 25 |     ListProducts,
 26 |     CreatePrice,
 27 |     ListPrices,
 28 |     CreatePaymentLink,
 29 |     ListInvoices,
 30 |     CreateInvoice,
 31 |     CreateInvoiceItem,
 32 |     FinalizeInvoice,
 33 |     RetrieveBalance,
 34 |     CreateRefund,
 35 |     ListPaymentIntents,
 36 |     CreateBillingPortalSession,
 37 | )
 38 | 
 39 | tools: List[Dict] = [
 40 |     {
 41 |         "method": "create_customer",
 42 |         "name": "Create Customer",
 43 |         "description": CREATE_CUSTOMER_PROMPT,
 44 |         "args_schema": CreateCustomer,
 45 |         "actions": {
 46 |             "customers": {
 47 |                 "create": True,
 48 |             }
 49 |         },
 50 |     },
 51 |     {
 52 |         "method": "list_customers",
 53 |         "name": "List Customers",
 54 |         "description": LIST_CUSTOMERS_PROMPT,
 55 |         "args_schema": ListCustomers,
 56 |         "actions": {
 57 |             "customers": {
 58 |                 "read": True,
 59 |             }
 60 |         },
 61 |     },
 62 |     {
 63 |         "method": "create_product",
 64 |         "name": "Create Product",
 65 |         "description": CREATE_PRODUCT_PROMPT,
 66 |         "args_schema": CreateProduct,
 67 |         "actions": {
 68 |             "products": {
 69 |                 "create": True,
 70 |             }
 71 |         },
 72 |     },
 73 |     {
 74 |         "method": "list_products",
 75 |         "name": "List Products",
 76 |         "description": LIST_PRODUCTS_PROMPT,
 77 |         "args_schema": ListProducts,
 78 |         "actions": {
 79 |             "products": {
 80 |                 "read": True,
 81 |             }
 82 |         },
 83 |     },
 84 |     {
 85 |         "method": "create_price",
 86 |         "name": "Create Price",
 87 |         "description": CREATE_PRICE_PROMPT,
 88 |         "args_schema": CreatePrice,
 89 |         "actions": {
 90 |             "prices": {
 91 |                 "create": True,
 92 |             }
 93 |         },
 94 |     },
 95 |     {
 96 |         "method": "list_prices",
 97 |         "name": "List Prices",
 98 |         "description": LIST_PRICES_PROMPT,
 99 |         "args_schema": ListPrices,
100 |         "actions": {
101 |             "prices": {
102 |                 "read": True,
103 |             }
104 |         },
105 |     },
106 |     {
107 |         "method": "create_payment_link",
108 |         "name": "Create Payment Link",
109 |         "description": CREATE_PAYMENT_LINK_PROMPT,
110 |         "args_schema": CreatePaymentLink,
111 |         "actions": {
112 |             "payment_links": {
113 |                 "create": True,
114 |             }
115 |         },
116 |     },
117 |     {
118 |         "method": "list_invoices",
119 |         "name": "List Invoices",
120 |         "description": LIST_INVOICES_PROMPT,
121 |         "args_schema": ListInvoices,
122 |         "actions": {
123 |             "invoices": {
124 |                 "read": True,
125 |             }
126 |         },
127 |     },
128 |     {
129 |         "method": "create_invoice",
130 |         "name": "Create Invoice",
131 |         "description": CREATE_INVOICE_PROMPT,
132 |         "args_schema": CreateInvoice,
133 |         "actions": {
134 |             "invoices": {
135 |                 "create": True,
136 |             }
137 |         },
138 |     },
139 |     {
140 |         "method": "create_invoice_item",
141 |         "name": "Create Invoice Item",
142 |         "description": CREATE_INVOICE_ITEM_PROMPT,
143 |         "args_schema": CreateInvoiceItem,
144 |         "actions": {
145 |             "invoice_items": {
146 |                 "create": True,
147 |             }
148 |         },
149 |     },
150 |     {
151 |         "method": "finalize_invoice",
152 |         "name": "Finalize Invoice",
153 |         "description": FINALIZE_INVOICE_PROMPT,
154 |         "args_schema": FinalizeInvoice,
155 |         "actions": {
156 |             "invoices": {
157 |                 "update": True,
158 |             }
159 |         },
160 |     },
161 |     {
162 |         "method": "retrieve_balance",
163 |         "name": "Retrieve Balance",
164 |         "description": RETRIEVE_BALANCE_PROMPT,
165 |         "args_schema": RetrieveBalance,
166 |         "actions": {
167 |             "balance": {
168 |                 "read": True,
169 |             }
170 |         },
171 |     },
172 |     {
173 |         "method": "create_refund",
174 |         "name": "Create Refund",
175 |         "description": CREATE_REFUND_PROMPT,
176 |         "args_schema": CreateRefund,
177 |         "actions": {
178 |             "refunds": {
179 |                 "create": True,
180 |             }
181 |         },
182 |     },
183 |     {
184 |         "method": "list_payment_intents",
185 |         "name": "List Payment Intents",
186 |         "description": LIST_PAYMENT_INTENTS_PROMPT,
187 |         "args_schema": ListPaymentIntents,
188 |         "actions": {
189 |             "payment_intents": {
190 |                 "read": True,
191 |             }
192 |         },
193 |     },
194 |     {
195 |         "method": "create_billing_portal_session",
196 |         "name": "Create Billing Portal Session",
197 |         "description": CREATE_BILLING_PORTAL_SESSION_PROMPT,
198 |         "args_schema": CreateBillingPortalSession,
199 |         "actions": {
200 |             "billing_portal_sessions": {
201 |                 "create": True,
202 |             }
203 |         },
204 |     },
205 | ]
206 | 
```

--------------------------------------------------------------------------------
/typescript/src/test/shared/invoices/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {createInvoice} from '@/shared/invoices/createInvoice';
  2 | import {listInvoices} from '@/shared/invoices/listInvoices';
  3 | import {finalizeInvoice} from '@/shared/invoices/finalizeInvoice';
  4 | 
  5 | const Stripe = jest.fn().mockImplementation(() => ({
  6 |   invoices: {
  7 |     create: jest.fn(),
  8 |     finalizeInvoice: jest.fn(),
  9 |     retrieve: jest.fn(),
 10 |     list: jest.fn(),
 11 |   },
 12 | }));
 13 | 
 14 | let stripe: ReturnType<typeof Stripe>;
 15 | 
 16 | beforeEach(() => {
 17 |   stripe = new Stripe('fake-api-key');
 18 | });
 19 | 
 20 | describe('createInvoice', () => {
 21 |   it('should create an invoice and return it', async () => {
 22 |     const params = {
 23 |       customer: 'cus_123456',
 24 |       days_until_due: 30,
 25 |     };
 26 | 
 27 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 28 | 
 29 |     const context = {};
 30 | 
 31 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 32 | 
 33 |     const result = await createInvoice(stripe, context, params);
 34 | 
 35 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 36 |       {...params, collection_method: 'send_invoice'},
 37 |       undefined
 38 |     );
 39 |     expect(result).toEqual(mockInvoice);
 40 |   });
 41 | 
 42 |   it('should specify the connected account if included in context', async () => {
 43 |     const params = {
 44 |       customer: 'cus_123456',
 45 |       days_until_due: 30,
 46 |     };
 47 | 
 48 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 49 | 
 50 |     const context = {
 51 |       account: 'acct_123456',
 52 |     };
 53 | 
 54 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 55 | 
 56 |     const result = await createInvoice(stripe, context, params);
 57 | 
 58 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 59 |       {
 60 |         ...params,
 61 |         collection_method: 'send_invoice',
 62 |       },
 63 |       {stripeAccount: context.account}
 64 |     );
 65 |     expect(result).toEqual(mockInvoice);
 66 |   });
 67 | 
 68 |   it('should create an invoice with a customer if included in context', async () => {
 69 |     const params = {
 70 |       days_until_due: 30,
 71 |     };
 72 | 
 73 |     const mockInvoice = {id: 'in_123456', customer: 'cus_123456'};
 74 | 
 75 |     const context = {
 76 |       customer: 'cus_123456',
 77 |     };
 78 | 
 79 |     stripe.invoices.create.mockResolvedValue(mockInvoice);
 80 | 
 81 |     const result = await createInvoice(stripe, context, params);
 82 | 
 83 |     expect(stripe.invoices.create).toHaveBeenCalledWith(
 84 |       {
 85 |         ...params,
 86 |         customer: context.customer,
 87 |         collection_method: 'send_invoice',
 88 |       },
 89 |       undefined
 90 |     );
 91 |     expect(result).toEqual(mockInvoice);
 92 |   });
 93 | });
 94 | 
 95 | describe('listInvoices', () => {
 96 |   it('should list invoices and return them', async () => {
 97 |     const mockInvoices = [
 98 |       {id: 'in_123456', customer: 'cus_123456'},
 99 |       {id: 'in_789012', customer: 'cus_789012'},
100 |     ];
101 | 
102 |     const context = {};
103 | 
104 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
105 | 
106 |     const result = await listInvoices(stripe, context, {});
107 | 
108 |     expect(stripe.invoices.list).toHaveBeenCalledWith({}, undefined);
109 |     expect(result).toEqual(mockInvoices);
110 |   });
111 | 
112 |   it('should specify the connected account if included in context', async () => {
113 |     const mockInvoices = [
114 |       {id: 'in_123456', customer: 'cus_123456'},
115 |       {id: 'in_789012', customer: 'cus_789012'},
116 |     ];
117 | 
118 |     const context = {
119 |       account: 'acct_123456',
120 |     };
121 | 
122 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
123 | 
124 |     const result = await listInvoices(stripe, context, {});
125 | 
126 |     expect(stripe.invoices.list).toHaveBeenCalledWith(
127 |       {},
128 |       {stripeAccount: context.account}
129 |     );
130 |     expect(result).toEqual(mockInvoices);
131 |   });
132 | 
133 |   it('should list invoices for a specific customer', async () => {
134 |     const mockInvoices = [
135 |       {id: 'in_123456', customer: 'cus_123456'},
136 |       {id: 'in_789012', customer: 'cus_789012'},
137 |     ];
138 | 
139 |     const context = {
140 |       customer: 'cus_123456',
141 |     };
142 | 
143 |     stripe.invoices.list.mockResolvedValue({data: mockInvoices});
144 | 
145 |     const result = await listInvoices(stripe, context, {});
146 | 
147 |     expect(stripe.invoices.list).toHaveBeenCalledWith(
148 |       {customer: context.customer},
149 |       undefined
150 |     );
151 |     expect(result).toEqual(mockInvoices);
152 |   });
153 | });
154 | 
155 | describe('finalizeInvoice', () => {
156 |   it('should finalize an invoice and return it', async () => {
157 |     const invoiceId = 'in_123456';
158 | 
159 |     const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
160 | 
161 |     const context = {};
162 | 
163 |     stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
164 | 
165 |     const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
166 | 
167 |     expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(
168 |       invoiceId,
169 |       undefined
170 |     );
171 |     expect(result).toEqual(mockInvoice);
172 |   });
173 | 
174 |   it('should specify the connected account if included in context', async () => {
175 |     const invoiceId = 'in_123456';
176 | 
177 |     const mockInvoice = {id: invoiceId, customer: 'cus_123456'};
178 | 
179 |     const context = {
180 |       account: 'acct_123456',
181 |     };
182 | 
183 |     stripe.invoices.finalizeInvoice.mockResolvedValue(mockInvoice);
184 | 
185 |     const result = await finalizeInvoice(stripe, context, {invoice: invoiceId});
186 | 
187 |     expect(stripe.invoices.finalizeInvoice).toHaveBeenCalledWith(invoiceId, {
188 |       stripeAccount: context.account,
189 |     });
190 |     expect(result).toEqual(mockInvoice);
191 |   });
192 | });
193 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/schema.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Optional
  2 | from pydantic import BaseModel, Field
  3 | 
  4 | 
  5 | class CreateCustomer(BaseModel):
  6 |     """Schema for the ``create_customer`` operation."""
  7 | 
  8 |     name: str = Field(
  9 |         ...,
 10 |         description="The name of the customer.",
 11 |     )
 12 | 
 13 |     email: Optional[str] = Field(
 14 |         None,
 15 |         description="The email of the customer.",
 16 |     )
 17 | 
 18 | 
 19 | class ListCustomers(BaseModel):
 20 |     """Schema for the ``list_customers`` operation."""
 21 | 
 22 |     limit: Optional[int] = Field(
 23 |         None,
 24 |         description=(
 25 |             "A limit on the number of objects to be returned."
 26 |             " Limit can range between 1 and 100."
 27 |         ),
 28 |     )
 29 | 
 30 |     email: Optional[str] = Field(
 31 |         None,
 32 |         description=(
 33 |             "A case-sensitive filter on the list based on"
 34 |             " the customer's email field. The value must be a string."
 35 |         ),
 36 |     )
 37 | 
 38 | 
 39 | class CreateProduct(BaseModel):
 40 |     """Schema for the ``create_product`` operation."""
 41 | 
 42 |     name: str = Field(
 43 |         ...,
 44 |         description="The name of the product.",
 45 |     )
 46 |     description: Optional[str] = Field(
 47 |         None,
 48 |         description="The description of the product.",
 49 |     )
 50 | 
 51 | 
 52 | class ListProducts(BaseModel):
 53 |     """Schema for the ``list_products`` operation."""
 54 | 
 55 |     limit: Optional[int] = Field(
 56 |         None,
 57 |         description=(
 58 |             "A limit on the number of objects to be returned."
 59 |             " Limit can range between 1 and 100, and the default is 10."
 60 |         ),
 61 |     )
 62 | 
 63 | 
 64 | class CreatePrice(BaseModel):
 65 |     """Schema for the ``create_price`` operation."""
 66 | 
 67 |     product: str = Field(
 68 |         ..., description="The ID of the product to create the price for."
 69 |     )
 70 |     unit_amount: int = Field(
 71 |         ...,
 72 |         description="The unit amount of the price in cents.",
 73 |     )
 74 |     currency: str = Field(
 75 |         ...,
 76 |         description="The currency of the price.",
 77 |     )
 78 | 
 79 | 
 80 | class ListPrices(BaseModel):
 81 |     """Schema for the ``list_prices`` operation."""
 82 | 
 83 |     product: Optional[str] = Field(
 84 |         None,
 85 |         description="The ID of the product to list prices for.",
 86 |     )
 87 |     limit: Optional[int] = Field(
 88 |         None,
 89 |         description=(
 90 |             "A limit on the number of objects to be returned."
 91 |             " Limit can range between 1 and 100, and the default is 10."
 92 |         ),
 93 |     )
 94 | 
 95 | 
 96 | class CreatePaymentLink(BaseModel):
 97 |     """Schema for the ``create_payment_link`` operation."""
 98 | 
 99 |     price: str = Field(
100 |         ...,
101 |         description="The ID of the price to create the payment link for.",
102 |     )
103 |     quantity: int = Field(
104 |         ...,
105 |         description="The quantity of the product to include.",
106 |     )
107 |     redirect_url: Optional[str] = Field(
108 |         None,
109 |         description="The URL the customer will be redirected to after the purchase is complete.",
110 |     )
111 | 
112 | 
113 | class ListInvoices(BaseModel):
114 |     """Schema for the ``list_invoices`` operation."""
115 | 
116 |     customer: Optional[str] = Field(
117 |         None,
118 |         description="The ID of the customer to list invoices for.",
119 |     )
120 |     limit: Optional[int] = Field(
121 |         None,
122 |         description=(
123 |             "A limit on the number of objects to be returned."
124 |             " Limit can range between 1 and 100, and the default is 10."
125 |         ),
126 |     )
127 | 
128 | 
129 | class CreateInvoice(BaseModel):
130 |     """Schema for the ``create_invoice`` operation."""
131 | 
132 |     customer: str = Field(
133 |         ..., description="The ID of the customer to create the invoice for."
134 |     )
135 | 
136 |     days_until_due: Optional[int] = Field(
137 |         None,
138 |         description="The number of days until the invoice is due.",
139 |     )
140 | 
141 | 
142 | class CreateInvoiceItem(BaseModel):
143 |     """Schema for the ``create_invoice_item`` operation."""
144 | 
145 |     customer: str = Field(
146 |         ...,
147 |         description="The ID of the customer to create the invoice item for.",
148 |     )
149 |     price: str = Field(
150 |         ...,
151 |         description="The ID of the price for the item.",
152 |     )
153 |     invoice: str = Field(
154 |         ...,
155 |         description="The ID of the invoice to create the item for.",
156 |     )
157 | 
158 | 
159 | class FinalizeInvoice(BaseModel):
160 |     """Schema for the ``finalize_invoice`` operation."""
161 | 
162 |     invoice: str = Field(
163 |         ...,
164 |         description="The ID of the invoice to finalize.",
165 |     )
166 | 
167 | 
168 | class RetrieveBalance(BaseModel):
169 |     """Schema for the ``retrieve_balance`` operation."""
170 | 
171 |     pass
172 | 
173 | 
174 | class CreateRefund(BaseModel):
175 |     """Schema for the ``create_refund`` operation."""
176 | 
177 |     payment_intent: str = Field(
178 |         ...,
179 |         description="The ID of the PaymentIntent to refund.",
180 |     )
181 |     amount: Optional[int] = Field(
182 |         ...,
183 |         description="The amount to refund in cents.",
184 |     )
185 | 
186 | class ListPaymentIntents(BaseModel):
187 |     """Schema for the ``list_payment_intents`` operation."""
188 | 
189 |     customer: Optional[str] = Field(
190 |         None,
191 |         description="The ID of the customer to list payment intents for.",
192 |     )
193 |     limit: Optional[int] = Field(
194 |         None,
195 |         description=(
196 |             "A limit on the number of objects to be returned."
197 |             " Limit can range between 1 and 100."
198 |         ),
199 |     )
200 | 
201 | class CreateBillingPortalSession(BaseModel):
202 |     """Schema for the ``create_billing_portal_session`` operation."""
203 | 
204 |     customer: str = Field(
205 |         None,
206 |         description="The ID of the customer to create the billing portal session for.",
207 |     )
208 |     return_url: Optional[str] = Field(
209 |         None,
210 |         description=(
211 |             "The default URL to return to afterwards."
212 |         ),
213 |     )
214 | 
```

--------------------------------------------------------------------------------
/typescript/src/modelcontextprotocol/register-paid-tool.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {z, type ZodRawShape} from 'zod';
  2 | import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
  3 | import {ToolCallback} from '@modelcontextprotocol/sdk/server/mcp.js';
  4 | import type {CallToolResult} from '@modelcontextprotocol/sdk/types.js';
  5 | import Stripe from 'stripe';
  6 | 
  7 | /*
  8 |  * This supports one-time payment, subscription, usage-based metered payment.
  9 |  * For usage-based, set a `meterEvent`
 10 |  */
 11 | export type PaidToolOptions = {
 12 |   paymentReason: string;
 13 |   meterEvent?: string;
 14 |   stripeSecretKey: string;
 15 |   userEmail: string;
 16 |   checkout: Stripe.Checkout.SessionCreateParams;
 17 | };
 18 | 
 19 | export async function registerPaidTool<Args extends ZodRawShape>(
 20 |   mcpServer: McpServer,
 21 |   toolName: string,
 22 |   toolDescription: string,
 23 |   paramsSchema: Args,
 24 |   // @ts-ignore: The typescript compiler complains this is an infinitely deep type
 25 |   paidCallback: ToolCallback<Args>,
 26 |   options: PaidToolOptions
 27 | ) {
 28 |   const priceId = options.checkout.line_items?.find((li) => li.price)?.price;
 29 | 
 30 |   if (!priceId) {
 31 |     throw new Error(
 32 |       'Price ID is required for a paid MCP tool. Learn more about prices: https://docs.stripe.com/products-prices/how-products-and-prices-work'
 33 |     );
 34 |   }
 35 | 
 36 |   const stripe = new Stripe(options.stripeSecretKey, {
 37 |     appInfo: {
 38 |       name: 'stripe-agent-toolkit-mcp-payments',
 39 |       version: '0.7.11',
 40 |       url: 'https://github.com/stripe/agent-toolkit',
 41 |     },
 42 |   });
 43 | 
 44 |   const getCurrentCustomerID = async () => {
 45 |     const customers = await stripe.customers.list({
 46 |       email: options.userEmail,
 47 |     });
 48 |     let customerId: null | string = null;
 49 |     if (customers.data.length > 0) {
 50 |       customerId =
 51 |         customers.data.find((customer) => {
 52 |           return customer.email === options.userEmail;
 53 |         })?.id || null;
 54 |     }
 55 |     if (!customerId) {
 56 |       const customer = await stripe.customers.create({
 57 |         email: options.userEmail,
 58 |       });
 59 |       customerId = customer.id;
 60 |     }
 61 | 
 62 |     return customerId;
 63 |   };
 64 | 
 65 |   const isToolPaidFor = async (toolName: string, customerId: string) => {
 66 |     // Check for paid checkout session for this tool (by metadata)
 67 |     const sessions = await stripe.checkout.sessions.list({
 68 |       customer: customerId,
 69 |       limit: 100,
 70 |     });
 71 |     const paidSession = sessions.data.find(
 72 |       (session) =>
 73 |         session.metadata?.toolName === toolName &&
 74 |         session.payment_status === 'paid'
 75 |     );
 76 | 
 77 |     if (paidSession?.subscription) {
 78 |       // Check for active subscription for the priceId
 79 |       const subs = await stripe.subscriptions.list({
 80 |         customer: customerId || '',
 81 |         status: 'active',
 82 |       });
 83 |       const activeSub = subs.data.find((sub) =>
 84 |         sub.items.data.find((item) => item.price.id === priceId)
 85 |       );
 86 |       if (activeSub) {
 87 |         return true;
 88 |       }
 89 |     }
 90 | 
 91 |     if (paidSession) {
 92 |       return true;
 93 |     }
 94 |     return false;
 95 |   };
 96 | 
 97 |   const createCheckoutSession = async (
 98 |     paymentType: string,
 99 |     customerId: string
100 |   ): Promise<CallToolResult | null> => {
101 |     try {
102 |       const session = await stripe.checkout.sessions.create({
103 |         ...options.checkout,
104 |         metadata: {
105 |           ...options.checkout.metadata,
106 |           toolName,
107 |         },
108 |         customer: customerId || undefined,
109 |       });
110 |       const result = {
111 |         status: 'payment_required',
112 |         data: {
113 |           paymentType,
114 |           checkoutUrl: session.url,
115 |           paymentReason: options.paymentReason,
116 |         },
117 |       };
118 |       return {
119 |         content: [
120 |           {
121 |             type: 'text',
122 |             text: JSON.stringify(result),
123 |           } as {type: 'text'; text: string},
124 |         ],
125 |       };
126 |     } catch (error: unknown) {
127 |       let errMsg = 'Unknown error';
128 |       if (typeof error === 'object' && error !== null) {
129 |         if (
130 |           'raw' in error &&
131 |           typeof (error as {raw?: {message?: string}}).raw?.message === 'string'
132 |         ) {
133 |           errMsg = (error as {raw: {message: string}}).raw.message;
134 |         } else if (
135 |           'message' in error &&
136 |           typeof (error as {message?: string}).message === 'string'
137 |         ) {
138 |           errMsg = (error as {message: string}).message;
139 |         }
140 |       }
141 |       console.error('Error creating stripe checkout session', errMsg);
142 |       return {
143 |         content: [
144 |           {
145 |             type: 'text',
146 |             text: JSON.stringify({
147 |               status: 'error',
148 |               error: errMsg,
149 |             }),
150 |           } as {type: 'text'; text: string},
151 |         ],
152 |         isError: true,
153 |       };
154 |     }
155 |   };
156 | 
157 |   const recordUsage = async (customerId: string) => {
158 |     if (!options.meterEvent) return;
159 |     await stripe.billing.meterEvents.create({
160 |       event_name: options.meterEvent,
161 |       payload: {
162 |         stripe_customer_id: customerId,
163 |         value: '1',
164 |       },
165 |     });
166 |   };
167 | 
168 |   // biome-ignore lint/suspicious/noExplicitAny: <explanation>
169 |   const callback = async (args: any, extra: any): Promise<CallToolResult> => {
170 |     const customerId = await getCurrentCustomerID();
171 |     const paidForTool = await isToolPaidFor(toolName, customerId);
172 |     const paymentType = options.meterEvent
173 |       ? 'usageBased'
174 |       : 'oneTimeSubscription';
175 |     if (!paidForTool) {
176 |       const checkoutResult = await createCheckoutSession(
177 |         paymentType,
178 |         customerId
179 |       );
180 |       if (checkoutResult) return checkoutResult;
181 |     }
182 |     if (paymentType === 'usageBased') {
183 |       await recordUsage(customerId);
184 |     }
185 |     // @ts-ignore: The typescript compiler complains this is an infinitely deep type
186 |     return paidCallback(args, extra);
187 |   };
188 | 
189 |   // @ts-ignore: The typescript compiler complains this is an infinitely deep type
190 |   mcpServer.tool(toolName, toolDescription, paramsSchema, callback as any);
191 | 
192 |   await Promise.resolve();
193 | }
194 | 
```

--------------------------------------------------------------------------------
/evals/scorer.ts:
--------------------------------------------------------------------------------

```typescript
  1 | require("dotenv").config();
  2 | 
  3 | import { ClosedQA } from "autoevals";
  4 | import every from "lodash/every";
  5 | import braintrustOpenai from "./braintrust_openai";
  6 | import { EvalOutput } from "./eval";
  7 | import { ChatCompletionMessageParam } from "openai/resources/chat/completions.mjs";
  8 | import { ChatCompletionMessageToolCall } from "openai/resources/chat/completions.mjs";
  9 | import { Configuration as StripeAgentToolkitConfig } from "../typescript/src/shared/configuration";
 10 | import isEqual from "lodash/isEqual";
 11 | 
 12 | /*
 13 |  * EvalInput is what is passed into the agent.
 14 |  * It contains a userPrompt and configuration that can be
 15 |  * used to override the toolkit configuration.
 16 |  */
 17 | export type EvalInput = {
 18 |   toolkitConfigOverride: StripeAgentToolkitConfig;
 19 |   userPrompt: string;
 20 | };
 21 | 
 22 | /*
 23 |  * EvalCaseFunction is the helper function that is used to
 24 |  * run assertions on the output of the agent. It does some
 25 |  * parsing of the raw completetion messages and tool calls
 26 |  * to make it easier to write assertions.
 27 |  */
 28 | export type EvalCaseFunction = ({
 29 |   toolCalls,
 30 |   messages,
 31 |   assistantMessages,
 32 | }: {
 33 |   toolCalls: ChatCompletionMessageToolCall[];
 34 |   messages: ChatCompletionMessageParam[];
 35 |   assistantMessages: string[];
 36 | }) => Array<AssertionResult | Promise<AssertionResult>>;
 37 | 
 38 | export const AssertionScorer = async ({
 39 |   output: responseMessages,
 40 |   expected: evalCaseFunction,
 41 | }: {
 42 |   output: EvalOutput;
 43 |   expected: EvalCaseFunction;
 44 | }) => {
 45 |   const toolCalls = responseMessages.flatMap((m) => {
 46 |     if ("tool_calls" in m && m.tool_calls) {
 47 |       return m.tool_calls;
 48 |     } else {
 49 |       return [];
 50 |     }
 51 |   });
 52 | 
 53 |   const assistantMessages = responseMessages
 54 |     .filter((m) => m.role === "assistant")
 55 |     .map((m) => (typeof m.content === "string" ? m.content : ""));
 56 | 
 57 |   const rawResults = evalCaseFunction({
 58 |     toolCalls,
 59 |     messages: responseMessages,
 60 |     assistantMessages,
 61 |   });
 62 | 
 63 |   const assertionResults = await Promise.all(rawResults);
 64 | 
 65 |   const allPassed = every(assertionResults, (r) => r.status === "passed");
 66 | 
 67 |   return {
 68 |     name: "Assertions Score",
 69 |     score: allPassed ? 1 : 0,
 70 |     metadata: {
 71 |       assertionResults,
 72 |     },
 73 |   };
 74 | };
 75 | 
 76 | /*
 77 | Below are assertion functions that can be used to evaluate the output of the agent.
 78 | Similar to test framework helpers like Jest.
 79 | */
 80 | 
 81 | export type AssertionResult = {
 82 |   status: "passed" | "failed";
 83 |   assertion_type: string;
 84 |   expected?: string;
 85 |   actualValue?: string;
 86 |   message?: string;
 87 | };
 88 | 
 89 | /**
 90 |  * Uses an LLM call to classify if a substring is semantically contained in a text.
 91 |  * @param text1 The full text you want to check against
 92 |  * @param text2 The string you want to check if it is contained in the text
 93 |  */
 94 | export async function semanticContains({
 95 |   text1,
 96 |   text2,
 97 | }: {
 98 |   text1: string;
 99 |   text2: string;
100 | }): Promise<AssertionResult> {
101 |   const system = `
102 |     You are a highly intelligent AI that can determine if a piece of text semantically contains another piece of text.
103 |     You will be given two pieces of text and you need to determine if the first piece of text semantically contains the second piece of text.
104 |     Answer with just "yes" or "no".
105 |     `;
106 | 
107 |   const completion = await braintrustOpenai.chat.completions.create({
108 |     model: "gpt-4o",
109 |     messages: [
110 |       { role: "system", content: system },
111 |       {
112 |         role: "user",
113 |         content: `Text 1: ${text1}\n\nText 2: ${text2}\n\nDoes Text 1 semantically contain Text 2? Answer with just "yes" or "no".`,
114 |       },
115 |     ],
116 |   });
117 | 
118 |   const response = completion.choices[0].message.content?.toLowerCase();
119 |   return {
120 |     status: response === "yes" ? "passed" : "failed",
121 |     assertion_type: "semantic_contains",
122 |     expected: text2,
123 |     actualValue: text1,
124 |   };
125 | }
126 | 
127 | export const expectToolCall = (
128 |   actualToolCalls: ChatCompletionMessageToolCall[],
129 |   expectedToolCalls: string[]
130 | ): AssertionResult => {
131 |   const actualToolCallNames = actualToolCalls.map((tc) => tc.function.name);
132 | 
133 |   const pass = actualToolCallNames.some((tc) => expectedToolCalls.includes(tc));
134 | 
135 |   return {
136 |     status: pass ? "passed" : "failed",
137 |     assertion_type: "expectToolCall",
138 |     expected: expectedToolCalls.join(", "),
139 |     actualValue: actualToolCallNames.join(", "),
140 |   };
141 | };
142 | 
143 | export const expectToolCallArgs = (
144 |   actualToolCalls: ChatCompletionMessageToolCall[],
145 |   expectedArgs: Array<{ name: string; arguments: any; shallow?: boolean }>
146 | ): AssertionResult => {
147 |   const actualToolCallNamesAndArgs = actualToolCalls.map((tc) => ({
148 |     name: tc.function.name,
149 |     arguments: JSON.parse(tc.function.arguments),
150 |   }));
151 |   const pass = actualToolCallNamesAndArgs.some((tc) => {
152 |     return expectedArgs.some((ea) => {
153 |       if (ea.name !== tc.name) {
154 |         return false;
155 |       }
156 | 
157 |       if (ea.shallow === true) {
158 |         return Object.keys(ea.arguments).every((key) => {
159 |           return isEqual(ea.arguments[key], tc.arguments[key]);
160 |         });
161 |       } else {
162 |         return isEqual(ea.arguments, tc.arguments);
163 |       }
164 |     });
165 |   });
166 |   return {
167 |     status: pass ? "passed" : "failed",
168 |     assertion_type: "expectToolCallArgs",
169 |     expected: expectedArgs
170 |       .map((ea) => `${ea.name}: ${JSON.stringify(ea.arguments)}`)
171 |       .join(", "),
172 |     actualValue: actualToolCallNamesAndArgs
173 |       .map((tc) => `${tc.name}: ${JSON.stringify(tc.arguments)}`)
174 |       .join(", "),
175 |   };
176 | };
177 | 
178 | export const llmCriteriaMet = async (
179 |   messages: ChatCompletionMessageParam[],
180 |   criteria: string
181 | ): Promise<AssertionResult> => {
182 |   const assistantMessages = messages
183 |     .filter((m) => m.role === "assistant")
184 |     .map((m) => m.content)
185 |     .join("\n");
186 | 
187 |   const closedQA = await ClosedQA({
188 |     client: braintrustOpenai,
189 |     input: "According to the provided criterion is the submission correct?",
190 |     criteria,
191 |     output: assistantMessages,
192 |   });
193 | 
194 |   const pass = !!closedQA.score && closedQA.score > 0.5;
195 | 
196 |   return {
197 |     status: pass ? "passed" : "failed",
198 |     assertion_type: "llm_criteria_met",
199 |     expected: criteria,
200 |     actualValue: assistantMessages,
201 |   };
202 | };
203 | 
204 | export const assert = (
205 |   condition: boolean,
206 |   message: string
207 | ): AssertionResult => {
208 |   return {
209 |     status: condition ? "passed" : "failed",
210 |     assertion_type: "plain_assert",
211 |     message,
212 |   };
213 | };
214 | 
```

--------------------------------------------------------------------------------
/modelcontextprotocol/src/test/index.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {main} from '../index';
  2 | import {parseArgs} from '../index';
  3 | import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
  4 | import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | describe('parseArgs function', () => {
  6 |   describe('success cases', () => {
  7 |     it('should parse api-key, tools and stripe-header arguments correctly', () => {
  8 |       const args = [
  9 |         '--api-key=sk_test_123',
 10 |         '--tools=all',
 11 |         '--stripe-account=acct_123',
 12 |       ];
 13 |       const options = parseArgs(args);
 14 |       expect(options.apiKey).toBe('sk_test_123');
 15 |       expect(options.tools).toEqual(['all']);
 16 |       expect(options.stripeAccount).toBe('acct_123');
 17 |     });
 18 | 
 19 |     it('should parse api-key and tools arguments correctly', () => {
 20 |       const args = ['--api-key=sk_test_123', '--tools=all'];
 21 |       const options = parseArgs(args);
 22 |       expect(options.apiKey).toBe('sk_test_123');
 23 |       expect(options.tools).toEqual(['all']);
 24 |     });
 25 | 
 26 |     it('should parse restricted api key correctly', () => {
 27 |       const args = ['--api-key=rk_test_123', '--tools=all'];
 28 |       const options = parseArgs(args);
 29 |       expect(options.apiKey).toBe('rk_test_123');
 30 |       expect(options.tools).toEqual(['all']);
 31 |     });
 32 | 
 33 |     it('if api key set in env variable, should parse tools argument correctly', () => {
 34 |       process.env.STRIPE_SECRET_KEY = 'sk_test_123';
 35 |       const args = ['--tools=all'];
 36 |       const options = parseArgs(args);
 37 |       expect(options.apiKey).toBe('sk_test_123');
 38 |       expect(options.tools).toEqual(['all']);
 39 |     });
 40 | 
 41 |     it('if api key set in env variable but also passed into args, should prefer args key', () => {
 42 |       process.env.STRIPE_SECRET_KEY = 'sk_test_123';
 43 |       const args = ['--api-key=sk_test_456', '--tools=all'];
 44 |       const options = parseArgs(args);
 45 |       expect(options.apiKey).toBe('sk_test_456');
 46 |       expect(options.tools).toEqual(['all']);
 47 |       delete process.env.STRIPE_SECRET_KEY;
 48 |     });
 49 | 
 50 |     it('should parse tools argument correctly if a list of tools is provided', () => {
 51 |       const args = [
 52 |         '--api-key=sk_test_123',
 53 |         '--tools=customers.create,products.read,documentation.read',
 54 |       ];
 55 |       const options = parseArgs(args);
 56 |       expect(options.tools).toEqual([
 57 |         'customers.create',
 58 |         'products.read',
 59 |         'documentation.read',
 60 |       ]);
 61 |       expect(options.apiKey).toBe('sk_test_123');
 62 |     });
 63 | 
 64 |     it('ignore all arguments not prefixed with --', () => {
 65 |       const args = [
 66 |         '--api-key=sk_test_123',
 67 |         '--tools=all',
 68 |         'stripe-account=acct_123',
 69 |       ];
 70 |       const options = parseArgs(args);
 71 |       expect(options.apiKey).toBe('sk_test_123');
 72 |       expect(options.tools).toEqual(['all']);
 73 |       expect(options.stripeAccount).toBeUndefined();
 74 |     });
 75 |   });
 76 | 
 77 |   describe('error cases', () => {
 78 |     it("should throw an error if api-key does not start with 'sk_' or 'rk_'", () => {
 79 |       const args = ['--api-key=test_123', '--tools=all'];
 80 |       expect(() => parseArgs(args)).toThrow(
 81 |         'API key must start with "sk_" or "rk_".'
 82 |       );
 83 |     });
 84 | 
 85 |     it('should throw an error if api-key is not provided', () => {
 86 |       const args = ['--tools=all'];
 87 |       expect(() => parseArgs(args)).toThrow(
 88 |         'Stripe API key not provided. Please either pass it as an argument --api-key=$KEY or set the STRIPE_SECRET_KEY environment variable.'
 89 |       );
 90 |     });
 91 | 
 92 |     it("should throw an error if stripe-account does not start with 'acct_'", () => {
 93 |       const args = [
 94 |         '--api-key=sk_test_123',
 95 |         '--tools=all',
 96 |         '--stripe-account=test_123',
 97 |       ];
 98 |       expect(() => parseArgs(args)).toThrow(
 99 |         'Stripe account must start with "acct_".'
100 |       );
101 |     });
102 | 
103 |     it('should throw an error if tools argument is not provided', () => {
104 |       const args = ['--api-key=sk_test_123'];
105 |       expect(() => parseArgs(args)).toThrow(
106 |         'The --tools arguments must be provided.'
107 |       );
108 |     });
109 | 
110 |     it('should throw an error if an invalid argument is provided', () => {
111 |       const args = [
112 |         '--invalid-arg=value',
113 |         '--api-key=sk_test_123',
114 |         '--tools=all',
115 |       ];
116 |       expect(() => parseArgs(args)).toThrow(
117 |         'Invalid argument: invalid-arg. Accepted arguments are: api-key, tools, stripe-account'
118 |       );
119 |     });
120 | 
121 |     it('should throw an error if tools is not in accepted tool list', () => {
122 |       const args = [
123 |         '--api-key=sk_test_123',
124 |         '--tools=customers.create,products.read,fake.tool',
125 |       ];
126 |       expect(() => parseArgs(args)).toThrow(
127 |         /Invalid tool: fake\.tool\. Accepted tools are: .*$/
128 |       );
129 |     });
130 |   });
131 | });
132 | 
133 | jest.mock('@stripe/agent-toolkit/modelcontextprotocol');
134 | jest.mock('@modelcontextprotocol/sdk/server/stdio.js');
135 | 
136 | describe('main function', () => {
137 |   beforeEach(() => {
138 |     jest.clearAllMocks();
139 |   });
140 | 
141 |   it('should initialize the server with tools=all correctly', async () => {
142 |     process.argv = ['node', 'index.js', '--api-key=sk_test_123', '--tools=all'];
143 | 
144 |     await main();
145 | 
146 |     expect(StripeAgentToolkit).toHaveBeenCalledWith({
147 |       secretKey: 'sk_test_123',
148 |       configuration: {
149 |         actions: ALL_ACTIONS,
150 |         context: {mode: 'modelcontextprotocol'},
151 |       },
152 |     });
153 | 
154 |     expect(StdioServerTransport).toHaveBeenCalled();
155 |   });
156 | 
157 |   it('should initialize the server with specific list of tools correctly', async () => {
158 |     process.argv = [
159 |       'node',
160 |       'index.js',
161 |       '--api-key=sk_test_123',
162 |       '--tools=customers.create,products.read,documentation.read',
163 |     ];
164 | 
165 |     await main();
166 | 
167 |     expect(StripeAgentToolkit).toHaveBeenCalledWith({
168 |       secretKey: 'sk_test_123',
169 |       configuration: {
170 |         actions: {
171 |           customers: {
172 |             create: true,
173 |           },
174 |           products: {
175 |             read: true,
176 |           },
177 |           documentation: {
178 |             read: true,
179 |           },
180 |         },
181 |         context: {
182 |           mode: 'modelcontextprotocol',
183 |         },
184 |       },
185 |     });
186 | 
187 |     expect(StdioServerTransport).toHaveBeenCalled();
188 |   });
189 | 
190 |   it('should initialize the server with stripe header', async () => {
191 |     process.argv = [
192 |       'node',
193 |       'index.js',
194 |       '--api-key=sk_test_123',
195 |       '--tools=all',
196 |       '--stripe-account=acct_123',
197 |     ];
198 | 
199 |     await main();
200 | 
201 |     expect(StripeAgentToolkit).toHaveBeenCalledWith({
202 |       secretKey: 'sk_test_123',
203 |       configuration: {
204 |         actions: ALL_ACTIONS,
205 |         context: {account: 'acct_123', mode: 'modelcontextprotocol'},
206 |       },
207 |     });
208 | 
209 |     expect(StdioServerTransport).toHaveBeenCalled();
210 |   });
211 | });
212 | 
213 | const ALL_ACTIONS = {
214 |   customers: {
215 |     create: true,
216 |     read: true,
217 |   },
218 |   coupons: {
219 |     create: true,
220 |     read: true,
221 |   },
222 |   invoices: {
223 |     create: true,
224 |     update: true,
225 |     read: true,
226 |   },
227 |   invoiceItems: {
228 |     create: true,
229 |   },
230 |   paymentLinks: {
231 |     create: true,
232 |   },
233 |   products: {
234 |     create: true,
235 |     read: true,
236 |   },
237 |   prices: {
238 |     create: true,
239 |     read: true,
240 |   },
241 |   balance: {
242 |     read: true,
243 |   },
244 |   refunds: {
245 |     create: true,
246 |   },
247 |   subscriptions: {
248 |     read: true,
249 |     update: true,
250 |   },
251 |   paymentIntents: {
252 |     read: true,
253 |   },
254 |   disputes: {
255 |     read: true,
256 |     update: true,
257 |   },
258 |   documentation: {
259 |     read: true,
260 |   },
261 | };
262 | 
```
Page 2/4FirstPrevNextLast