#
tokens: 35363/50000 9/192 files (page 3/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 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/functions.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import {listSubscriptions} from '@/shared/subscriptions/listSubscriptions';
  2 | import {cancelSubscription} from '@/shared/subscriptions/cancelSubscription';
  3 | import {updateSubscription} from '@/shared/subscriptions/updateSubscription';
  4 | 
  5 | const Stripe = jest.fn().mockImplementation(() => ({
  6 |   subscriptions: {
  7 |     list: jest.fn(),
  8 |     cancel: jest.fn(),
  9 |     update: jest.fn(),
 10 |   },
 11 | }));
 12 | 
 13 | let stripe: ReturnType<typeof Stripe>;
 14 | 
 15 | beforeEach(() => {
 16 |   stripe = new Stripe('fake-api-key');
 17 | });
 18 | 
 19 | describe('listSubscriptions', () => {
 20 |   it('should list subscriptions and return data', async () => {
 21 |     const mockSubscriptions = [
 22 |       {
 23 |         id: 'sub_123456',
 24 |         customer: 'cus_123456',
 25 |         status: 'active',
 26 |         current_period_start: 1609459200, // 2021-01-01
 27 |         current_period_end: 1612137600, // 2021-02-01
 28 |         items: {
 29 |           data: [
 30 |             {
 31 |               id: 'si_123',
 32 |               price: 'price_123',
 33 |               quantity: 1,
 34 |             },
 35 |           ],
 36 |         },
 37 |       },
 38 |       {
 39 |         id: 'sub_789012',
 40 |         customer: 'cus_123456',
 41 |         status: 'canceled',
 42 |         current_period_start: 1609459200, // 2021-01-01
 43 |         current_period_end: 1612137600, // 2021-02-01
 44 |         items: {
 45 |           data: [
 46 |             {
 47 |               id: 'si_456',
 48 |               price: 'price_456',
 49 |               quantity: 2,
 50 |             },
 51 |           ],
 52 |         },
 53 |       },
 54 |     ];
 55 | 
 56 |     const context = {};
 57 |     const params = {};
 58 | 
 59 |     stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
 60 |     const result = await listSubscriptions(stripe, context, params);
 61 | 
 62 |     expect(stripe.subscriptions.list).toHaveBeenCalledWith(params, undefined);
 63 |     expect(result).toEqual(mockSubscriptions);
 64 |   });
 65 | 
 66 |   it('should add customer from context if provided', async () => {
 67 |     const mockSubscriptions = [
 68 |       {
 69 |         id: 'sub_123456',
 70 |         customer: 'cus_123456',
 71 |         status: 'active',
 72 |         current_period_start: 1609459200,
 73 |         current_period_end: 1612137600,
 74 |         items: {
 75 |           data: [
 76 |             {
 77 |               id: 'si_123',
 78 |               price: 'price_123',
 79 |               quantity: 1,
 80 |             },
 81 |           ],
 82 |         },
 83 |       },
 84 |     ];
 85 | 
 86 |     const context = {
 87 |       customer: 'cus_123456',
 88 |     };
 89 |     const params = {};
 90 | 
 91 |     stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
 92 |     const result = await listSubscriptions(stripe, context, params);
 93 | 
 94 |     expect(stripe.subscriptions.list).toHaveBeenCalledWith(
 95 |       {customer: 'cus_123456'},
 96 |       undefined
 97 |     );
 98 |     expect(result).toEqual(mockSubscriptions);
 99 |   });
100 | 
101 |   it('should specify the connected account if included in context', async () => {
102 |     const mockSubscriptions = [
103 |       {
104 |         id: 'sub_123456',
105 |         customer: 'cus_123456',
106 |         status: 'active',
107 |         current_period_start: 1609459200,
108 |         current_period_end: 1612137600,
109 |         items: {
110 |           data: [
111 |             {
112 |               id: 'si_123',
113 |               price: 'price_123',
114 |               quantity: 1,
115 |             },
116 |           ],
117 |         },
118 |       },
119 |     ];
120 | 
121 |     const context = {
122 |       account: 'acct_123456',
123 |     };
124 |     const params = {};
125 | 
126 |     stripe.subscriptions.list.mockResolvedValue({data: mockSubscriptions});
127 |     const result = await listSubscriptions(stripe, context, params);
128 | 
129 |     expect(stripe.subscriptions.list).toHaveBeenCalledWith(params, {
130 |       stripeAccount: context.account,
131 |     });
132 |     expect(result).toEqual(mockSubscriptions);
133 |   });
134 | 
135 |   it('should handle errors gracefully', async () => {
136 |     const context = {};
137 |     const params = {};
138 | 
139 |     stripe.subscriptions.list.mockRejectedValue(new Error('API Error'));
140 |     const result = await listSubscriptions(stripe, context, params);
141 | 
142 |     expect(result).toBe('Failed to list subscriptions');
143 |   });
144 | });
145 | 
146 | describe('cancelSubscription', () => {
147 |   it('should cancel a subscription and return the result', async () => {
148 |     const mockSubscription = {
149 |       id: 'sub_123456',
150 |       customer: 'cus_123456',
151 |       status: 'active',
152 |       current_period_start: 1609459200,
153 |       current_period_end: 1612137600,
154 |       items: {
155 |         data: [
156 |           {
157 |             id: 'si_123',
158 |             price: 'price_123',
159 |             quantity: 1,
160 |           },
161 |         ],
162 |       },
163 |     };
164 | 
165 |     const context = {};
166 |     const params = {
167 |       subscription: 'sub_123456',
168 |     };
169 | 
170 |     stripe.subscriptions.cancel.mockResolvedValue(mockSubscription);
171 |     const result = await cancelSubscription(stripe, context, params);
172 | 
173 |     expect(stripe.subscriptions.cancel).toHaveBeenCalledWith(
174 |       'sub_123456',
175 |       {},
176 |       undefined
177 |     );
178 |     expect(result).toEqual(mockSubscription);
179 |   });
180 | 
181 |   it('should handle errors gracefully', async () => {
182 |     const context = {};
183 |     const params = {
184 |       subscription: 'sub_123456',
185 |     };
186 | 
187 |     stripe.subscriptions.cancel.mockRejectedValue(new Error('API Error'));
188 |     const result = await cancelSubscription(stripe, context, params);
189 | 
190 |     expect(result).toBe('Failed to cancel subscription');
191 |   });
192 | });
193 | 
194 | describe('updateSubscription', () => {
195 |   it('should update a subscription and return the result', async () => {
196 |     const mockSubscription = {
197 |       id: 'sub_123456',
198 |       customer: 'cus_123456',
199 |       status: 'active',
200 |       current_period_start: 1609459200,
201 |       current_period_end: 1612137600,
202 |       items: {
203 |         data: [
204 |           {
205 |             id: 'si_123',
206 |             price: 'price_123',
207 |             quantity: 1,
208 |           },
209 |         ],
210 |       },
211 |     };
212 | 
213 |     const context = {};
214 |     const params = {
215 |       subscription: 'sub_123456',
216 |       items: [
217 |         {
218 |           id: 'si_123',
219 |           quantity: 2,
220 |         },
221 |       ],
222 |     };
223 | 
224 |     stripe.subscriptions.update.mockResolvedValue(mockSubscription);
225 |     const result = await updateSubscription(stripe, context, params);
226 | 
227 |     expect(stripe.subscriptions.update).toHaveBeenCalledWith(
228 |       'sub_123456',
229 |       {
230 |         items: [
231 |           {
232 |             id: 'si_123',
233 |             quantity: 2,
234 |           },
235 |         ],
236 |       },
237 |       undefined
238 |     );
239 |     expect(result).toEqual(mockSubscription);
240 |   });
241 | 
242 |   it('should handle errors gracefully', async () => {
243 |     const context = {};
244 |     const params = {
245 |       subscription: 'sub_123456',
246 |       items: [
247 |         {
248 |           id: 'si_123',
249 |           quantity: 2,
250 |         },
251 |       ],
252 |     };
253 | 
254 |     stripe.subscriptions.update.mockRejectedValue(new Error('API Error'));
255 |     const result = await updateSubscription(stripe, context, params);
256 | 
257 |     expect(result).toBe('Failed to update subscription');
258 |   });
259 | 
260 |   it('should specify the connected account if included in context', async () => {
261 |     const mockSubscription = {
262 |       id: 'sub_123456',
263 |       customer: 'cus_123456',
264 |       status: 'active',
265 |       current_period_start: 1609459200,
266 |       current_period_end: 1612137600,
267 |       items: {
268 |         data: [
269 |           {
270 |             id: 'si_123',
271 |             price: 'price_123',
272 |             quantity: 1,
273 |           },
274 |         ],
275 |       },
276 |     };
277 | 
278 |     const context = {
279 |       account: 'acct_123456',
280 |     };
281 |     const params = {
282 |       subscription: 'sub_123456',
283 |       cancel_at_period_end: true,
284 |     };
285 | 
286 |     stripe.subscriptions.update.mockResolvedValue(mockSubscription);
287 |     const result = await updateSubscription(stripe, context, params);
288 | 
289 |     expect(stripe.subscriptions.update).toHaveBeenCalledWith(
290 |       'sub_123456',
291 |       {
292 |         cancel_at_period_end: true,
293 |       },
294 |       {
295 |         stripeAccount: context.account,
296 |       }
297 |     );
298 |     expect(result).toEqual(mockSubscription);
299 |   });
300 | });
301 | 
```

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

```typescript
  1 | import {z} from 'zod';
  2 | import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
  3 | import {registerPaidTool} from '../../modelcontextprotocol/register-paid-tool';
  4 | import Stripe from 'stripe';
  5 | import type {
  6 |   ServerNotification,
  7 |   ServerRequest,
  8 | } from '@modelcontextprotocol/sdk/types.js';
  9 | import type {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js';
 10 | 
 11 | // Mock Stripe
 12 | jest.mock('stripe');
 13 | const mockSecretKey = 'sk_test_123';
 14 | 
 15 | describe('registerPaidTool', () => {
 16 |   let mockMcpServer: jest.Mocked<McpServer>;
 17 |   let mockStripe: jest.Mocked<any>;
 18 |   let mockExtra: RequestHandlerExtra<ServerRequest, ServerNotification>;
 19 | 
 20 |   beforeEach(() => {
 21 |     // Reset all mocks
 22 |     jest.clearAllMocks();
 23 | 
 24 |     // Mock McpServer
 25 |     mockMcpServer = {
 26 |       tool: jest.fn(),
 27 |     } as any;
 28 | 
 29 |     // Mock Stripe instance and methods
 30 |     mockStripe = {
 31 |       customers: {
 32 |         list: jest.fn(),
 33 |         create: jest.fn(),
 34 |       },
 35 |       checkout: {
 36 |         sessions: {
 37 |           create: jest.fn(),
 38 |           retrieve: jest.fn(),
 39 |           list: jest.fn(),
 40 |         },
 41 |       },
 42 |       subscriptions: {
 43 |         list: jest.fn(),
 44 |       },
 45 |       billing: {
 46 |         meterEvents: {
 47 |           create: jest.fn(),
 48 |         },
 49 |       },
 50 |     };
 51 | 
 52 |     (Stripe as unknown as jest.Mock).mockImplementation(() => mockStripe);
 53 | 
 54 |     // Mock request handler extra
 55 |     mockExtra = {
 56 |       signal: new AbortController().signal,
 57 |       sendNotification: jest.fn(),
 58 |       sendRequest: jest.fn(),
 59 |       requestId: '123',
 60 |     };
 61 |   });
 62 | 
 63 |   it('should register a tool with the McpServer', async () => {
 64 |     const toolName = 'testTool';
 65 |     const toolDescription = 'Test tool description';
 66 |     const paramsSchema = {
 67 |       testParam: z.string(),
 68 |     };
 69 |     const callback = jest.fn();
 70 | 
 71 |     // @ts-ignore: https://github.com/modelcontextprotocol/typescript-sdk/issues/494
 72 |     await registerPaidTool(
 73 |       mockMcpServer,
 74 |       toolName,
 75 |       toolDescription,
 76 |       paramsSchema,
 77 |       callback,
 78 |       {
 79 |         paymentReason: 'Test payment',
 80 |         stripeSecretKey: mockSecretKey,
 81 |         userEmail: '[email protected]',
 82 |         checkout: {
 83 |           success_url: 'https://example.com/success',
 84 |           line_items: [{price: 'price_123', quantity: 1}],
 85 |           mode: 'subscription',
 86 |         },
 87 |       }
 88 |     );
 89 | 
 90 |     expect(mockMcpServer.tool).toHaveBeenCalledWith(
 91 |       toolName,
 92 |       toolDescription,
 93 |       paramsSchema,
 94 |       expect.any(Function)
 95 |     );
 96 |   });
 97 | 
 98 |   it('should create a new customer if one does not exist', async () => {
 99 |     mockStripe.customers.list.mockResolvedValue({data: []});
100 |     mockStripe.customers.create.mockResolvedValue({id: 'cus_123'});
101 |     mockStripe.subscriptions.list.mockResolvedValue({
102 |       data: [
103 |         {
104 |           items: {
105 |             data: [
106 |               {
107 |                 price: {
108 |                   id: 'price_123',
109 |                 },
110 |               },
111 |             ],
112 |           },
113 |         },
114 |       ],
115 |     });
116 |     mockStripe.checkout.sessions.list.mockResolvedValue({data: []});
117 |     mockStripe.checkout.sessions.create.mockResolvedValue({
118 |       id: 'cs_123',
119 |       url: 'https://checkout.stripe.com/123',
120 |     });
121 | 
122 |     const toolName = 'testTool';
123 |     const callback = jest.fn();
124 | 
125 |     await registerPaidTool(
126 |       mockMcpServer,
127 |       toolName,
128 |       'Test description',
129 |       {testParam: z.string()},
130 |       callback,
131 |       {
132 |         paymentReason: 'Test payment',
133 |         stripeSecretKey: mockSecretKey,
134 |         userEmail: '[email protected]',
135 |         checkout: {
136 |           success_url: 'https://example.com/success',
137 |           line_items: [{price: 'price_123', quantity: 1}],
138 |           mode: 'subscription',
139 |         },
140 |       }
141 |     );
142 | 
143 |     const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
144 |     // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
145 |     await registeredCallback({testParam: 'test'}, mockExtra);
146 | 
147 |     expect(mockStripe.customers.list).toHaveBeenCalledWith({
148 |       email: '[email protected]',
149 |     });
150 |     expect(mockStripe.customers.create).toHaveBeenCalledWith({
151 |       email: '[email protected]',
152 |     });
153 |   });
154 | 
155 |   it('should create a checkout session for unpaid tools', async () => {
156 |     mockStripe.customers.list.mockResolvedValue({
157 |       data: [{id: 'cus_123', email: '[email protected]'}],
158 |     });
159 |     mockStripe.checkout.sessions.create.mockResolvedValue({
160 |       id: 'cs_123',
161 |       url: 'https://checkout.stripe.com/123',
162 |     });
163 |     mockStripe.subscriptions.list.mockResolvedValue({
164 |       data: [], // No active subscriptions
165 |     });
166 |     mockStripe.checkout.sessions.list.mockResolvedValue({
167 |       data: [], // No paid sessions
168 |     });
169 | 
170 |     const toolName = 'testTool';
171 |     const callback = jest.fn();
172 | 
173 |     await registerPaidTool(
174 |       mockMcpServer,
175 |       toolName,
176 |       'Test description',
177 |       {testParam: z.string()},
178 |       callback,
179 |       {
180 |         paymentReason: 'Test payment',
181 |         stripeSecretKey: mockSecretKey,
182 |         userEmail: '[email protected]',
183 |         checkout: {
184 |           success_url: 'https://example.com/success',
185 |           line_items: [{price: 'price_123', quantity: 1}],
186 |           mode: 'subscription',
187 |         },
188 |       }
189 |     );
190 | 
191 |     const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
192 |     // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
193 |     const result = await registeredCallback({testParam: 'test'}, mockExtra);
194 | 
195 |     expect(mockStripe.checkout.sessions.create).toHaveBeenCalledWith({
196 |       success_url: 'https://example.com/success',
197 |       line_items: [
198 |         {
199 |           price: 'price_123',
200 |           quantity: 1,
201 |         },
202 |       ],
203 |       mode: 'subscription',
204 |       customer: 'cus_123',
205 |       metadata: {toolName},
206 |     });
207 |     expect(result).toEqual({
208 |       content: [
209 |         {
210 |           type: 'text',
211 |           text: JSON.stringify({
212 |             status: 'payment_required',
213 |             data: {
214 |               paymentType: 'oneTimeSubscription',
215 |               checkoutUrl: 'https://checkout.stripe.com/123',
216 |               paymentReason: 'Test payment',
217 |             },
218 |           }),
219 |         },
220 |       ],
221 |     });
222 |     expect(callback).not.toHaveBeenCalled();
223 |   });
224 | 
225 |   it('should handle usage-based billing when meterEvent is provided', async () => {
226 |     const toolName = 'testTool';
227 |     mockStripe.customers.list.mockResolvedValue({
228 |       data: [{id: 'cus_123', email: '[email protected]'}],
229 |     });
230 |     mockStripe.checkout.sessions.list.mockResolvedValue({
231 |       data: [
232 |         {
233 |           id: 'cs_123',
234 |           metadata: {toolName},
235 |           payment_status: 'paid',
236 |           subscription: 'sub_123',
237 |         },
238 |       ],
239 |     });
240 |     mockStripe.subscriptions.list.mockResolvedValue({
241 |       data: [
242 |         {
243 |           items: {
244 |             data: [
245 |               {
246 |                 price: {
247 |                   id: 'price_123',
248 |                 },
249 |               },
250 |             ],
251 |           },
252 |         },
253 |       ],
254 |     });
255 |     const callback = jest.fn().mockResolvedValue({
256 |       content: [{type: 'text', text: 'Success'}],
257 |     });
258 | 
259 |     await registerPaidTool(
260 |       mockMcpServer,
261 |       toolName,
262 |       'Test description',
263 |       {testParam: z.string()},
264 |       callback,
265 |       {
266 |         paymentReason: 'Test payment',
267 |         meterEvent: 'test.event',
268 |         stripeSecretKey: mockSecretKey,
269 |         userEmail: '[email protected]',
270 |         checkout: {
271 |           success_url: 'https://example.com/success',
272 |           line_items: [{price: 'price_123'}],
273 |           mode: 'subscription',
274 |         },
275 |       }
276 |     );
277 | 
278 |     const registeredCallback = mockMcpServer.tool.mock.calls[0]?.[3];
279 |     // @ts-ignore: TypeScript can't disambiguate between params schema and annotations
280 |     await registeredCallback({testParam: 'test'}, mockExtra);
281 | 
282 |     expect(mockStripe.billing.meterEvents.create).toHaveBeenCalledWith({
283 |       event_name: 'test.event',
284 |       payload: {
285 |         stripe_customer_id: 'cus_123',
286 |         value: '1',
287 |       },
288 |     });
289 |   });
290 | });
291 | 
```

--------------------------------------------------------------------------------
/evals/cases.ts:
--------------------------------------------------------------------------------

```typescript
  1 | require("dotenv").config();
  2 | 
  3 | import {
  4 |   assert,
  5 |   EvalCaseFunction,
  6 |   EvalInput,
  7 |   expectToolCall,
  8 |   expectToolCallArgs,
  9 |   llmCriteriaMet,
 10 | } from "./scorer";
 11 | import { Configuration as StripeAgentToolkitConfig } from "../typescript/src/shared/configuration";
 12 | import Stripe from "stripe";
 13 | 
 14 | /*
 15 |  * A single test case that is used to evaluate the agent.
 16 |  * It contains an input, a toolkit config, and an function to use to run
 17 |  * assertions on the output of the agent. It is structured to be used with
 18 |  * Braintrust.
 19 |  */
 20 | type BraintrustTestCase = {
 21 |   input: EvalInput;
 22 |   toolkitConfig?: StripeAgentToolkitConfig;
 23 |   expected: EvalCaseFunction;
 24 | };
 25 | 
 26 | /* This is used in a Braintrust Eval. Our test framework appends new test cases to this array.*/
 27 | const _testCases: Array<BraintrustTestCase | Promise<BraintrustTestCase>> = [];
 28 | 
 29 | /*
 30 |  * Helper type for adding test cases to the Braintrust Eval.
 31 |  */
 32 | type TestCaseData = {
 33 |   // The user prompt to pass into the agent.
 34 |   prompt: string;
 35 |   // The function to use to run assertions on the output of the agent.
 36 |   fn: EvalCaseFunction;
 37 |   // Optional toolkit config to set into the agent to override the default set in eval.ts.
 38 |   toolkitConfig?: StripeAgentToolkitConfig;
 39 | };
 40 | 
 41 | const argsToTestCase = (args: TestCaseData): BraintrustTestCase => ({
 42 |   input: {
 43 |     toolkitConfigOverride: args.toolkitConfig || {},
 44 |     userPrompt: args.prompt,
 45 |   },
 46 |   expected: args.fn,
 47 | });
 48 | 
 49 | /*
 50 |  * Helper function for adding test cases to the Braintrust Eval.
 51 |  */
 52 | const test = (args: TestCaseData | (() => Promise<TestCaseData>)) => {
 53 |   if (typeof args == "function") {
 54 |     const promise = args().then(argsToTestCase);
 55 |     _testCases.push(promise);
 56 |   } else {
 57 |     _testCases.push(argsToTestCase(args));
 58 |   }
 59 | };
 60 | 
 61 | test({
 62 |   prompt:
 63 |     "Create a product called 'Test Product' with a description 'A test product for evaluation'",
 64 |   fn: ({ toolCalls, messages }) => [
 65 |     expectToolCall(toolCalls, ["create_product"]),
 66 |     llmCriteriaMet(
 67 |       messages,
 68 |       "The message should include a successful production creation response"
 69 |     ),
 70 |   ],
 71 | });
 72 | 
 73 | test({
 74 |   prompt: "List all available products",
 75 |   fn: ({ toolCalls, messages }) => [
 76 |     expectToolCall(toolCalls, ["list_products"]),
 77 |     llmCriteriaMet(messages, "The message should include a list of products"),
 78 |   ],
 79 | });
 80 | 
 81 | test({
 82 |   prompt:
 83 |     "Create a customer with a name of a Philadelphia Eagles player and email (you can make it up). Charge them $100.",
 84 |   fn: ({ toolCalls, messages }) => [
 85 |     expectToolCall(toolCalls, ["create_customer"]),
 86 |   ],
 87 | });
 88 | 
 89 | test({
 90 |   prompt:
 91 |     "Create a payment link for a new product called 'test' with a price of $70. Come up with a haiku for the description.",
 92 |   fn: ({ toolCalls, messages }) => [
 93 |     llmCriteriaMet(messages, "The message should include a payment link"),
 94 |     expectToolCall(toolCalls, ["create_payment_link"]),
 95 |   ],
 96 | });
 97 | 
 98 | test({
 99 |   prompt: 
100 |     "Create a payment link for a new product called 'test' with a price of $35.99, if the user completes the purchase they should be redirected to https://www.stripe.com",
101 |   fn: ({ toolCalls, messages }) => [
102 |     expectToolCall(toolCalls, ["create_payment_link"]),
103 |     expectToolCallArgs(toolCalls, [
104 |       {
105 |         name: "create_payment_link",
106 |         arguments: {
107 |           redirect_url: "https://www.stripe.com",
108 |         },
109 |         shallow: true,
110 |       },
111 |     ]),
112 |     llmCriteriaMet(messages, "The message should include a payment link and indicate the redirect url"),
113 |   ],
114 | });
115 | 
116 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
117 | 
118 | test(async () => {
119 |   const customer = await stripe.customers.create({
120 |     name: "Joel E",
121 |     email: "[email protected]",
122 |   });
123 | 
124 |   const joelsPayment = await stripe.paymentIntents.create({
125 |     amount: 2000,
126 |     currency: "usd",
127 |     customer: customer.id,
128 |   });
129 | 
130 |   const otherPi = await stripe.paymentIntents.create({
131 |     amount: 3000,
132 |     currency: "usd",
133 |   });
134 | 
135 |   return {
136 |     prompt: "List payment intents",
137 |     toolkitConfig: {
138 |       context: {
139 |         customer: customer.id,
140 |       },
141 |     },
142 |     fn: ({ assistantMessages }) => [
143 |       assert(
144 |         (function () {
145 |           return (
146 |             assistantMessages.some((m) => m.includes(joelsPayment.id)) &&
147 |             assistantMessages.every((m) => !m.includes(otherPi.id))
148 |           );
149 |         })(),
150 |         `messages only includes customers payment intent ${joelsPayment.id}`
151 |       ),
152 |     ],
153 |   };
154 | });
155 | 
156 | test({
157 |   prompt: "List all subscriptions",
158 |   fn: ({ toolCalls, messages }) => [
159 |     llmCriteriaMet(
160 |       messages,
161 |       "The message should include a list of subscriptions"
162 |     ),
163 |     expectToolCall(toolCalls, ["list_subscriptions"]),
164 |   ],
165 | });
166 | 
167 | test({
168 |   prompt: "Create a coupon called SUMMER25 that gives 25% off",
169 |   fn: ({ toolCalls, messages }) => [
170 |     expectToolCall(toolCalls, ["create_coupon"]),
171 |     llmCriteriaMet(
172 |       messages,
173 |       "The message should include a coupon creation response"
174 |     ),
175 |   ],
176 | });
177 | 
178 | test({
179 |   prompt: "Create a coupon called WINTERTEN that gives $10 off",
180 |   fn: ({ toolCalls, messages }) => [
181 |     expectToolCall(toolCalls, ["create_coupon"]),
182 |     expectToolCallArgs(toolCalls, [
183 |       {
184 |         name: "create_coupon",
185 |         arguments: {
186 |           amount_off: 1000,
187 |           currency: "USD",
188 |           name: "WINTERTEN",
189 |         }
190 |       },
191 |     ]),
192 |     llmCriteriaMet(
193 |       messages,
194 |       "The message should include a coupon creation response"
195 |     ),
196 |   ],
197 | });
198 | 
199 | test({
200 |   prompt: "List all coupons",
201 |   fn: ({ toolCalls, messages }) => [
202 |     expectToolCall(toolCalls, ["list_coupons"]),
203 |   ],
204 | });
205 | 
206 | test(async () => {
207 |   const customer = await stripe.customers.create({
208 |     name: "Joel E",
209 |     email: "[email protected]",
210 |     payment_method: "pm_card_visa",
211 |   });
212 | 
213 |   const paymentMethod = await stripe.paymentMethods.create({
214 |     type: "card",
215 |     card: {
216 |       token: "tok_visa",
217 |     },
218 |   });
219 | 
220 |   await stripe.paymentMethods.attach(paymentMethod.id, {
221 |     customer: customer.id,
222 |   });
223 | 
224 |   // // Set as default payment method
225 |   await stripe.customers.update(customer.id, {
226 |     invoice_settings: { default_payment_method: paymentMethod.id },
227 |   });
228 | 
229 |   const product = await stripe.products.create({
230 |     name: "Subscription Product",
231 |     description: "A test subscription product",
232 |   });
233 | 
234 |   const price = await stripe.prices.create({
235 |     product: product.id,
236 |     unit_amount: 1000,
237 |     currency: "usd",
238 |     recurring: { interval: "month" },
239 |   });
240 | 
241 |   await stripe.subscriptions.create({
242 |     customer: customer.id,
243 |     items: [{ price: price.id }],
244 |   });
245 | 
246 |   return {
247 |     prompt: `Cancel the users subscription`,
248 |     toolkitConfig: {
249 |       context: {
250 |         customer: customer.id,
251 |       },
252 |     },
253 |     fn: ({ toolCalls, messages }) => [
254 |       expectToolCall(toolCalls, ["list_subscriptions", "cancel_subscription"]),
255 |       llmCriteriaMet(
256 |         messages,
257 |         "The message should include a successful subscription cancellation response"
258 |       ),
259 |     ],
260 |   };
261 | });
262 | 
263 | // New test for update subscription
264 | test(async () => {
265 |   const customer = await stripe.customers.create({
266 |     name: "User Example",
267 |     email: "[email protected]",
268 |   });
269 | 
270 |   const paymentMethod = await stripe.paymentMethods.create({
271 |     type: "card",
272 |     card: {
273 |       token: "tok_visa",
274 |     },
275 |   });
276 | 
277 |   await stripe.paymentMethods.attach(paymentMethod.id, {
278 |     customer: customer.id,
279 |   });
280 | 
281 |   // // Set as default payment method
282 |   await stripe.customers.update(customer.id, {
283 |     invoice_settings: { default_payment_method: paymentMethod.id },
284 |   });
285 | 
286 |   const product = await stripe.products.create({
287 |     name: "SaaS Product",
288 |     description: "A test subscription product",
289 |   });
290 | 
291 |   const basicPrice = await stripe.prices.create({
292 |     product: product.id,
293 |     unit_amount: 1000,
294 |     currency: "usd",
295 |     recurring: { interval: "month" },
296 |   });
297 | 
298 |   const premiumPrice = await stripe.prices.create({
299 |     product: product.id,
300 |     unit_amount: 2000,
301 |     currency: "usd",
302 |     recurring: { interval: "month" },
303 |   });
304 | 
305 |   const subscription = await stripe.subscriptions.create({
306 |     customer: customer.id,
307 |     items: [{ price: basicPrice.id, quantity: 1 }],
308 |   });
309 | 
310 |   return {
311 |     prompt: `Upgrade the user's subscription to the premium plan`,
312 |     toolkitConfig: {
313 |       context: {
314 |         customer: customer.id,
315 |       },
316 |     },
317 |     fn: ({ toolCalls, messages }) => [
318 |       expectToolCall(toolCalls, ["list_subscriptions", "update_subscription"]),
319 |       llmCriteriaMet(
320 |         messages,
321 |         "The message should include a successful subscription update response. The subscription should have been updated to the premium plan and have only one item."
322 |       ),
323 |     ],
324 |   };
325 | });
326 | 
327 | test({
328 |   prompt: "List all disputes",
329 |   fn: ({ toolCalls, messages }) => [
330 |     expectToolCall(toolCalls, ["list_disputes"]),
331 |   ],
332 | });
333 | 
334 | export const getEvalTestCases = async () => Promise.all(_testCases);
335 | 
```

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

```python
  1 | # pyright: strict
  2 | 
  3 | import imaplib
  4 | import email
  5 | import smtplib
  6 | from email.mime.text import MIMEText
  7 | from email.message import Message
  8 | from email.mime.multipart import MIMEMultipart
  9 | from email.utils import parseaddr
 10 | from typing import List, Tuple, Callable, Union, Awaitable
 11 | import asyncio
 12 | import json
 13 | import re
 14 | from datetime import datetime
 15 | from email.utils import parsedate_to_datetime
 16 | 
 17 | 
 18 | class Email:
 19 |     def __init__(
 20 |         self,
 21 |         from_address: str,
 22 |         to_address: str,
 23 |         subject: str,
 24 |         body: str,
 25 |         id: str = "",
 26 |         date: datetime = datetime.now(),
 27 |     ):
 28 |         self.id = id
 29 |         self.to_address = to_address
 30 |         self.from_address = from_address
 31 |         self.subject = subject
 32 |         self.body = body
 33 |         self.date = date
 34 | 
 35 |     def to_message(self, reply_id: str, reply_to: str) -> MIMEMultipart:
 36 |         msg = MIMEMultipart()
 37 |         msg["From"] = self.from_address
 38 |         msg["To"] = self.to_address
 39 |         msg["Subject"] = self.subject
 40 |         msg["In-Reply-To"] = reply_id
 41 |         msg["References"] = reply_id
 42 |         msg["Reply-To"] = reply_to
 43 |         msg.attach(MIMEText(f"<html><body>{self.body}</body></html>", "html"))
 44 |         return msg
 45 | 
 46 |     def to_dict(self):
 47 |         return {
 48 |             "id": self.id,
 49 |             "to": self.to_address,
 50 |             "from": self.from_address,
 51 |             "subject": self.subject,
 52 |             "body": self.body,
 53 |             "date": self.date.strftime("%a, %d %b %Y %H:%M:%S %z"),
 54 |         }
 55 | 
 56 | 
 57 | class Emailer:
 58 |     """
 59 |     Emailer is an IMAP/SMTP client that can be used to fetch and respond to emails.
 60 |     It was mostly vibe-coded so please make improvements!
 61 |     TODO: add agent replies to the context
 62 |     """
 63 | 
 64 |     def __init__(
 65 |         self,
 66 |         email_address: str,
 67 |         email_password: str,
 68 |         support_address: str = "",
 69 |         imap_server: str = "imap.gmail.com",
 70 |         imap_port: int = 993,
 71 |         smtp_server: str = "smtp.gmail.com",
 72 |         smtp_port: int = 587,
 73 |     ):
 74 |         # Email configuration
 75 |         self.email_address = email_address
 76 |         self.support_address = support_address if support_address else email_address
 77 |         self.email_password = email_password
 78 |         self.imap_server = imap_server
 79 |         self.imap_port = imap_port
 80 |         self.smtp_server = smtp_server
 81 |         self.smtp_port = smtp_port
 82 | 
 83 |     def _connect_to_email(self) -> Tuple[imaplib.IMAP4_SSL, smtplib.SMTP]:
 84 |         """Establish connections to email servers."""
 85 |         # Connect to IMAP server
 86 |         imap_conn = imaplib.IMAP4_SSL(self.imap_server, self.imap_port)
 87 |         imap_conn.login(self.email_address, self.email_password)
 88 | 
 89 |         # Connect to SMTP server
 90 |         smtp_conn = smtplib.SMTP(self.smtp_server, self.smtp_port)
 91 |         smtp_conn.starttls()
 92 |         smtp_conn.login(self.email_address, self.email_password)
 93 | 
 94 |         return imap_conn, smtp_conn
 95 | 
 96 |     def _get_body(self, email_message: Message) -> str:
 97 |         body: str = ""
 98 |         if email_message.is_multipart():
 99 |             for part in email_message.walk():
100 |                 if part.get_content_type() == "text/plain":
101 |                     payload = part.get_payload(decode=True)
102 |                     if isinstance(payload, bytes):
103 |                         body = payload.decode()
104 |                     break
105 |         else:
106 |             payload = email_message.get_payload(decode=True)
107 |             if isinstance(payload, bytes):
108 |                 body = payload.decode()
109 |             else:
110 |                 body = str(payload)
111 |         return self._strip_replies(body)
112 | 
113 |     def _strip_replies(self, raw_body: str) -> str:
114 |         lines = raw_body.split("\n")
115 |         pruned: List[str] = []
116 |         for line in lines:
117 |             # Stop if we see a typical reply indicator
118 |             if line.strip().startswith("On ") and " wrote:" in line:
119 |                 break
120 |             pruned.append(line)
121 |         return "\n".join(pruned).strip()
122 | 
123 |     def _parse_email(
124 |         self, imap_conn: imaplib.IMAP4_SSL, email_id: bytes
125 |     ) -> Union[Email, None]:
126 |         _, msg_data = imap_conn.fetch(email_id.decode(), "(BODY.PEEK[])")
127 |         if not msg_data or not msg_data[0]:
128 |             return None
129 |         msg_resp = msg_data[0]
130 |         if isinstance(msg_resp, tuple) and len(msg_resp) == 2:
131 |             email_body = msg_resp[1]
132 |         else:
133 |             return None
134 | 
135 |         email_message = email.message_from_bytes(email_body)
136 |         subject = email_message["subject"] or ""
137 |         from_address = parseaddr(email_message.get("From", ""))[1]
138 |         to_address = parseaddr(email_message.get("To", ""))[1]
139 |         date_str = email_message.get("Date", "")
140 |         date = datetime.now()
141 |         if date_str:
142 |             try:
143 |                 date = parsedate_to_datetime(date_str)
144 |             except Exception:
145 |                 pass
146 | 
147 |         body = self._get_body(email_message)
148 |         return Email(
149 |             id=email_id.decode(),
150 |             from_address=from_address,
151 |             to_address=to_address,
152 |             subject=subject,
153 |             body=body,
154 |             date=date,
155 |         )
156 | 
157 |     def _get_email_thread(
158 |         self, imap_conn: imaplib.IMAP4_SSL, email_id_bytes: bytes
159 |     ) -> List[Email]:
160 |         email = self._parse_email(imap_conn, email_id_bytes)
161 |         if not email:
162 |             return []
163 | 
164 |         thread = [email]
165 | 
166 |         # Try thread via X-GM-THRID (Gmail extension)
167 |         _, thrid_data = imap_conn.fetch(email.id, "(X-GM-THRID)")
168 |         match = None
169 |         if thrid_data and thrid_data[0]:
170 |             data = thrid_data[0]
171 |             if isinstance(data, bytes):
172 |                 match = re.search(r"X-GM-THRID\s+(\d+)", data.decode())
173 |             else:
174 |                 match = re.search(r"X-GM-THRID\s+(\d+)", str(data))
175 |         if match:
176 |             thread_id = match.group(1)
177 |             _, thread_ids = imap_conn.search(None, f"X-GM-THRID {thread_id}")
178 |             if thread_ids and thread_ids[0]:
179 |                 thread = [
180 |                     self._parse_email(imap_conn, mid) for mid in thread_ids[0].split()
181 |                 ]
182 |                 thread = [e for e in thread if e]
183 |                 thread.sort(key=lambda e: e.date)
184 |                 return thread
185 | 
186 |         # Fallback: use REFERENCES header
187 |         _, ref_data = imap_conn.fetch(
188 |             email.id, "(BODY.PEEK[HEADER.FIELDS (REFERENCES)])"
189 |         )
190 |         if ref_data and ref_data[0]:
191 |             ref_line = (
192 |                 ref_data[0][1].decode() if isinstance(ref_data[0][1], bytes) else ""
193 |             )
194 |             refs = re.findall(r"<([^>]+)>", ref_line)
195 |             for ref in refs:
196 |                 _, ref_ids = imap_conn.search(None, f'(HEADER Message-ID "<{ref}>")')
197 |                 if ref_ids and ref_ids[0]:
198 |                     for ref_id in ref_ids[0].split():
199 |                         ref_email = self._parse_email(imap_conn, ref_id)
200 |                         if ref_email and ref_email.id not in [e.id for e in thread]:
201 |                             thread.append(ref_email)
202 | 
203 |             # Sort emails in the thread by date (ascending order)
204 |             thread.sort(key=lambda e: e.date)
205 |             return thread
206 | 
207 |         return thread
208 | 
209 |     def _get_unread_emails(self, imap_conn: imaplib.IMAP4_SSL) -> List[List[Email]]:
210 |         imap_conn.select("INBOX")
211 |         _, msg_nums = imap_conn.search(None, f'(UNSEEN TO "{self.support_address}")')
212 |         emails: List[List[Email]] = []
213 | 
214 |         for email_id in msg_nums[0].split():
215 |             thread = self._get_email_thread(imap_conn, email_id)
216 |             emails.append(thread)
217 | 
218 |         return emails
219 | 
220 |     def mark_as_read(self, imap_conn: imaplib.IMAP4_SSL, message_id: str):
221 |         imap_conn.store(message_id, "+FLAGS", "\\Seen")
222 | 
223 |     def get_email_thread(self, email_id: str) -> List[Email]:
224 |         # Connect to email servers
225 |         imap_conn, smtp_conn = self._connect_to_email()
226 |         imap_conn.select("INBOX")
227 | 
228 |         # Get the thread
229 |         thread = self._get_email_thread(
230 |             imap_conn=imap_conn, email_id_bytes=email_id.encode()
231 |         )
232 | 
233 |         # Close connections
234 |         imap_conn.logout()
235 |         smtp_conn.quit()
236 | 
237 |         return thread
238 | 
239 |     async def process(
240 |         self,
241 |         respond: Callable[[List[Email]], Awaitable[Union[Email, None]]],
242 |         mark_read: bool = True,
243 |     ):
244 |         # Connect to email servers
245 |         imap_conn, smtp_conn = self._connect_to_email()
246 | 
247 |         # Get unread emails
248 |         print("Fetching unread emails...")
249 |         unread_emails = self._get_unread_emails(imap_conn)
250 |         for email_thread in unread_emails:
251 |             # Get the most recent email in the thread
252 |             most_recent = email_thread[-1]
253 | 
254 |             # Generate the response
255 |             response = await respond(email_thread)
256 | 
257 |             # If there is no response, skip this email and keep as unread
258 |             # in the inbox
259 |             if response is None:
260 |                 continue
261 | 
262 |             # Send the response
263 |             # Get the most recent email in the thread to reply to
264 |             print(
265 |                 f"Replying to '{response.to_address}' with:\n  {json.dumps(response.body)}"
266 |             )
267 |             smtp_conn.send_message(
268 |                 response.to_message(most_recent.id, self.support_address)
269 |             )
270 | 
271 |             # Mark the original email as read
272 |             if mark_read:
273 |                 self.mark_as_read(imap_conn, most_recent.id)
274 | 
275 |         # Close connections
276 |         imap_conn.logout()
277 |         smtp_conn.quit()
278 | 
279 |     async def run(
280 |         self,
281 |         respond: Callable[[List[Email]], Awaitable[Union[Email, None]]],
282 |         mark_read: bool = True,
283 |         delay: int = 60,
284 |     ):
285 |         while True:
286 |             # Process emails
287 |             await self.process(respond, mark_read)
288 |             # Wait before next check
289 |             print(f"Sleeping for {delay}s...")
290 |             await asyncio.sleep(delay)
291 | 
```

--------------------------------------------------------------------------------
/modelcontextprotocol/eslint.config.mjs:
--------------------------------------------------------------------------------

```
  1 | import prettier from "eslint-plugin-prettier";
  2 | import _import from "eslint-plugin-import";
  3 | import { fixupPluginRules } from "@eslint/compat";
  4 | import globals from "globals";
  5 | import typescriptEslint from "@typescript-eslint/eslint-plugin";
  6 | import path from "node:path";
  7 | import { fileURLToPath } from "node:url";
  8 | import js from "@eslint/js";
  9 | import { FlatCompat } from "@eslint/eslintrc";
 10 | 
 11 | const __filename = fileURLToPath(import.meta.url);
 12 | const __dirname = path.dirname(__filename);
 13 | const compat = new FlatCompat({
 14 |     baseDirectory: __dirname,
 15 |     recommendedConfig: js.configs.recommended,
 16 |     allConfig: js.configs.all
 17 | });
 18 | 
 19 | export default [...compat.extends("plugin:prettier/recommended"), {
 20 |     plugins: {
 21 |         prettier,
 22 |         import: fixupPluginRules(_import),
 23 |     },
 24 | 
 25 |     languageOptions: {
 26 |         globals: {
 27 |             ...globals.node,
 28 |         },
 29 | 
 30 |         ecmaVersion: 2018,
 31 |         sourceType: "commonjs",
 32 |     },
 33 | 
 34 |     rules: {
 35 |         "accessor-pairs": "error",
 36 |         "array-bracket-spacing": ["error", "never"],
 37 |         "array-callback-return": "off",
 38 |         "arrow-parens": "error",
 39 |         "arrow-spacing": "error",
 40 |         "block-scoped-var": "off",
 41 |         "block-spacing": "off",
 42 | 
 43 |         "brace-style": ["error", "1tbs", {
 44 |             allowSingleLine: true,
 45 |         }],
 46 | 
 47 |         "capitalized-comments": "off",
 48 |         "class-methods-use-this": "off",
 49 |         "comma-dangle": "off",
 50 |         "comma-spacing": "off",
 51 |         "comma-style": ["error", "last"],
 52 |         complexity: "error",
 53 |         "computed-property-spacing": ["error", "never"],
 54 |         "consistent-return": "off",
 55 |         "consistent-this": "off",
 56 |         curly: "error",
 57 |         "default-case": "off",
 58 |         "dot-location": ["error", "property"],
 59 |         "dot-notation": "error",
 60 |         "eol-last": "error",
 61 |         eqeqeq: "off",
 62 |         "func-call-spacing": "error",
 63 |         "func-name-matching": "error",
 64 |         "func-names": "off",
 65 | 
 66 |         "func-style": ["error", "declaration", {
 67 |             allowArrowFunctions: true,
 68 |         }],
 69 | 
 70 |         "generator-star-spacing": "error",
 71 |         "global-require": "off",
 72 |         "guard-for-in": "error",
 73 |         "handle-callback-err": "off",
 74 |         "id-blacklist": "error",
 75 |         "id-length": "off",
 76 |         "id-match": "error",
 77 |         "import/extensions": "off",
 78 |         "init-declarations": "off",
 79 |         "jsx-quotes": "error",
 80 |         "key-spacing": "error",
 81 | 
 82 |         "keyword-spacing": ["error", {
 83 |             after: true,
 84 |             before: true,
 85 |         }],
 86 | 
 87 |         "line-comment-position": "off",
 88 |         "linebreak-style": ["error", "unix"],
 89 |         "lines-around-directive": "error",
 90 |         "max-depth": "error",
 91 |         "max-len": "off",
 92 |         "max-lines": "off",
 93 |         "max-nested-callbacks": "error",
 94 |         "max-params": "off",
 95 |         "max-statements": "off",
 96 |         "max-statements-per-line": "off",
 97 |         "multiline-ternary": "off",
 98 |         "new-cap": "off",
 99 |         "new-parens": "error",
100 |         "newline-after-var": "off",
101 |         "newline-before-return": "off",
102 |         "newline-per-chained-call": "off",
103 |         "no-alert": "error",
104 |         "no-array-constructor": "error",
105 |         "no-await-in-loop": "error",
106 |         "no-bitwise": "off",
107 |         "no-caller": "error",
108 |         "no-catch-shadow": "off",
109 |         "no-compare-neg-zero": "error",
110 |         "no-confusing-arrow": "error",
111 |         "no-continue": "off",
112 |         "no-div-regex": "error",
113 |         "no-duplicate-imports": "off",
114 |         "no-else-return": "off",
115 |         "no-empty-function": "off",
116 |         "no-eq-null": "off",
117 |         "no-eval": "error",
118 |         "no-extend-native": "error",
119 |         "no-extra-bind": "error",
120 |         "no-extra-label": "error",
121 |         "no-extra-parens": "off",
122 |         "no-floating-decimal": "error",
123 |         "no-implicit-globals": "error",
124 |         "no-implied-eval": "error",
125 |         "no-inline-comments": "off",
126 |         "no-inner-declarations": ["error", "functions"],
127 |         "no-invalid-this": "off",
128 |         "no-iterator": "error",
129 |         "no-label-var": "error",
130 |         "no-labels": "error",
131 |         "no-lone-blocks": "error",
132 |         "no-lonely-if": "error",
133 |         "no-loop-func": "error",
134 |         "no-magic-numbers": "off",
135 |         "no-mixed-requires": "error",
136 |         "no-multi-assign": "off",
137 |         "no-multi-spaces": "error",
138 |         "no-multi-str": "error",
139 |         "no-multiple-empty-lines": "error",
140 |         "no-native-reassign": "error",
141 |         "no-negated-condition": "off",
142 |         "no-negated-in-lhs": "error",
143 |         "no-nested-ternary": "error",
144 |         "no-new": "error",
145 |         "no-new-func": "error",
146 |         "no-new-object": "error",
147 |         "no-new-require": "error",
148 |         "no-new-wrappers": "error",
149 |         "no-octal-escape": "error",
150 |         "no-param-reassign": "off",
151 |         "no-path-concat": "error",
152 | 
153 |         "no-plusplus": ["error", {
154 |             allowForLoopAfterthoughts: true,
155 |         }],
156 | 
157 |         "no-process-env": "off",
158 |         "no-process-exit": "error",
159 |         "no-proto": "error",
160 |         "no-prototype-builtins": "off",
161 |         "no-restricted-globals": "error",
162 |         "no-restricted-imports": "error",
163 |         "no-restricted-modules": "error",
164 |         "no-restricted-properties": "error",
165 |         "no-restricted-syntax": "error",
166 |         "no-return-assign": "error",
167 |         "no-return-await": "error",
168 |         "no-script-url": "error",
169 |         "no-self-compare": "error",
170 |         "no-sequences": "error",
171 |         "no-shadow": "off",
172 |         "no-shadow-restricted-names": "error",
173 |         "no-spaced-func": "error",
174 |         "no-sync": "error",
175 |         "no-tabs": "error",
176 |         "no-template-curly-in-string": "error",
177 |         "no-ternary": "off",
178 |         "no-throw-literal": "error",
179 |         "no-trailing-spaces": "error",
180 |         "no-undef-init": "error",
181 |         "no-undefined": "off",
182 |         "no-underscore-dangle": "off",
183 |         "no-unmodified-loop-condition": "error",
184 |         "no-unneeded-ternary": "error",
185 |         "no-unused-expressions": "error",
186 | 
187 |         "no-unused-vars": ["error", {
188 |             args: "none",
189 |         }],
190 | 
191 |         "no-use-before-define": "off",
192 |         "no-useless-call": "error",
193 |         "no-useless-computed-key": "error",
194 |         "no-useless-concat": "error",
195 |         "no-useless-constructor": "error",
196 |         "no-useless-escape": "off",
197 |         "no-useless-rename": "error",
198 |         "no-useless-return": "error",
199 |         "no-var": "off",
200 |         "no-void": "error",
201 |         "no-warning-comments": "error",
202 |         "no-whitespace-before-property": "error",
203 |         "no-with": "error",
204 |         "nonblock-statement-body-position": "error",
205 |         "object-curly-newline": "off",
206 |         "object-curly-spacing": ["error", "never"],
207 |         "object-property-newline": "off",
208 |         "object-shorthand": "off",
209 |         "one-var": "off",
210 |         "one-var-declaration-per-line": "error",
211 |         "operator-assignment": ["error", "always"],
212 |         "operator-linebreak": "off",
213 |         "padded-blocks": "off",
214 |         "prefer-arrow-callback": "off",
215 |         "prefer-const": "error",
216 | 
217 |         "prefer-destructuring": ["error", {
218 |             array: false,
219 |             object: false,
220 |         }],
221 | 
222 |         "prefer-numeric-literals": "error",
223 |         "prefer-promise-reject-errors": "error",
224 |         "prefer-reflect": "off",
225 |         "prefer-rest-params": "off",
226 |         "prefer-spread": "off",
227 |         "prefer-template": "off",
228 |         "quote-props": "off",
229 | 
230 |         quotes: ["error", "single", {
231 |             avoidEscape: true,
232 |         }],
233 | 
234 |         radix: "error",
235 |         "require-await": "error",
236 |         "require-jsdoc": "off",
237 |         "rest-spread-spacing": "error",
238 |         semi: "off",
239 | 
240 |         "semi-spacing": ["error", {
241 |             after: true,
242 |             before: false,
243 |         }],
244 | 
245 |         "sort-imports": "off",
246 |         "sort-keys": "off",
247 |         "sort-vars": "error",
248 |         "space-before-blocks": "error",
249 |         "space-before-function-paren": "off",
250 |         "space-in-parens": ["error", "never"],
251 |         "space-infix-ops": "error",
252 |         "space-unary-ops": "error",
253 |         "spaced-comment": ["error", "always"],
254 |         strict: "off",
255 |         "symbol-description": "error",
256 |         "template-curly-spacing": "error",
257 |         "template-tag-spacing": "error",
258 |         "unicode-bom": ["error", "never"],
259 |         "valid-jsdoc": "off",
260 |         "vars-on-top": "off",
261 |         "wrap-regex": "off",
262 |         "yield-star-spacing": "error",
263 |         yoda: ["error", "never"],
264 |     },
265 | }, ...compat.extends(
266 |     "eslint:recommended",
267 |     "plugin:@typescript-eslint/eslint-recommended",
268 |     "plugin:@typescript-eslint/recommended",
269 |     "plugin:prettier/recommended",
270 | ).map(config => ({
271 |     ...config,
272 |     files: ["**/*.ts"],
273 | })), {
274 |     files: ["**/*.ts"],
275 | 
276 |     plugins: {
277 |         "@typescript-eslint": typescriptEslint,
278 |         prettier,
279 |     },
280 | 
281 |     rules: {
282 |         "@typescript-eslint/no-use-before-define": 0,
283 |         "@typescript-eslint/no-empty-interface": 0,
284 |         "@typescript-eslint/no-unused-vars": 0,
285 |         "@typescript-eslint/triple-slash-reference": 0,
286 |         "@typescript-eslint/ban-ts-comment": "off",
287 |         "@typescript-eslint/no-empty-function": 0,
288 |         "@typescript-eslint/no-require-imports": 0,
289 | 
290 |         "@typescript-eslint/naming-convention": ["error", {
291 |             selector: "default",
292 |             format: ["camelCase", "UPPER_CASE", "PascalCase"],
293 |             leadingUnderscore: "allow",
294 |         }, {
295 |             selector: "property",
296 |             format: null,
297 |         }],
298 | 
299 |         "@typescript-eslint/no-explicit-any": 0,
300 |         "@typescript-eslint/explicit-function-return-type": "off",
301 |         "@typescript-eslint/no-this-alias": "off",
302 |         "@typescript-eslint/no-var-requires": 0,
303 |         "prefer-rest-params": "off",
304 |     },
305 | }, {
306 |     files: ["test/**/*.ts"],
307 | 
308 |     rules: {
309 |         "@typescript-eslint/explicit-function-return-type": "off",
310 |     },
311 | }];
312 | 
```

--------------------------------------------------------------------------------
/typescript/eslint.config.mjs:
--------------------------------------------------------------------------------

```
  1 | import prettier from "eslint-plugin-prettier";
  2 | import _import from "eslint-plugin-import";
  3 | import { fixupPluginRules } from "@eslint/compat";
  4 | import globals from "globals";
  5 | import typescriptEslint from "@typescript-eslint/eslint-plugin";
  6 | import path from "node:path";
  7 | import { fileURLToPath } from "node:url";
  8 | import js from "@eslint/js";
  9 | import { FlatCompat } from "@eslint/eslintrc";
 10 | 
 11 | const __filename = fileURLToPath(import.meta.url);
 12 | const __dirname = path.dirname(__filename);
 13 | const compat = new FlatCompat({
 14 |     baseDirectory: __dirname,
 15 |     recommendedConfig: js.configs.recommended,
 16 |     allConfig: js.configs.all
 17 | });
 18 | 
 19 | export default [...compat.extends("plugin:prettier/recommended"), {
 20 |     plugins: {
 21 |         prettier,
 22 |         import: fixupPluginRules(_import),
 23 |     },
 24 | 
 25 |     languageOptions: {
 26 |         globals: {
 27 |             ...globals.node,
 28 |         },
 29 | 
 30 |         ecmaVersion: 2018,
 31 |         sourceType: "commonjs",
 32 |     },
 33 | 
 34 |     rules: {
 35 |         "accessor-pairs": "error",
 36 |         "array-bracket-spacing": ["error", "never"],
 37 |         "array-callback-return": "off",
 38 |         "arrow-parens": "error",
 39 |         "arrow-spacing": "error",
 40 |         "block-scoped-var": "off",
 41 |         "block-spacing": "off",
 42 | 
 43 |         "brace-style": ["error", "1tbs", {
 44 |             allowSingleLine: true,
 45 |         }],
 46 | 
 47 |         "capitalized-comments": "off",
 48 |         "class-methods-use-this": "off",
 49 |         "comma-dangle": "off",
 50 |         "comma-spacing": "off",
 51 |         "comma-style": ["error", "last"],
 52 |         complexity: "error",
 53 |         "computed-property-spacing": ["error", "never"],
 54 |         "consistent-return": "off",
 55 |         "consistent-this": "off",
 56 |         curly: "error",
 57 |         "default-case": "off",
 58 |         "dot-location": ["error", "property"],
 59 |         "dot-notation": "error",
 60 |         "eol-last": "error",
 61 |         eqeqeq: "off",
 62 |         "func-call-spacing": "error",
 63 |         "func-name-matching": "error",
 64 |         "func-names": "off",
 65 | 
 66 |         "func-style": ["error", "declaration", {
 67 |             allowArrowFunctions: true,
 68 |         }],
 69 | 
 70 |         "generator-star-spacing": "error",
 71 |         "global-require": "off",
 72 |         "guard-for-in": "error",
 73 |         "handle-callback-err": "off",
 74 |         "id-blacklist": "error",
 75 |         "id-length": "off",
 76 |         "id-match": "error",
 77 |         "import/extensions": "off",
 78 |         "init-declarations": "off",
 79 |         "jsx-quotes": "error",
 80 |         "key-spacing": "error",
 81 | 
 82 |         "keyword-spacing": ["error", {
 83 |             after: true,
 84 |             before: true,
 85 |         }],
 86 | 
 87 |         "line-comment-position": "off",
 88 |         "linebreak-style": ["error", "unix"],
 89 |         "lines-around-directive": "error",
 90 |         "max-depth": "error",
 91 |         "max-len": "off",
 92 |         "max-lines": "off",
 93 |         "max-nested-callbacks": "error",
 94 |         "max-params": "off",
 95 |         "max-statements": "off",
 96 |         "max-statements-per-line": "off",
 97 |         "multiline-ternary": "off",
 98 |         "new-cap": "off",
 99 |         "new-parens": "error",
100 |         "newline-after-var": "off",
101 |         "newline-before-return": "off",
102 |         "newline-per-chained-call": "off",
103 |         "no-alert": "error",
104 |         "no-array-constructor": "error",
105 |         "no-await-in-loop": "error",
106 |         "no-bitwise": "off",
107 |         "no-caller": "error",
108 |         "no-catch-shadow": "off",
109 |         "no-compare-neg-zero": "error",
110 |         "no-confusing-arrow": "error",
111 |         "no-continue": "off",
112 |         "no-div-regex": "error",
113 |         "no-duplicate-imports": "off",
114 |         "no-else-return": "off",
115 |         "no-empty-function": "off",
116 |         "no-eq-null": "off",
117 |         "no-eval": "error",
118 |         "no-extend-native": "error",
119 |         "no-extra-bind": "error",
120 |         "no-extra-label": "error",
121 |         "no-extra-parens": "off",
122 |         "no-floating-decimal": "error",
123 |         "no-implicit-globals": "error",
124 |         "no-implied-eval": "error",
125 |         "no-inline-comments": "off",
126 |         "no-inner-declarations": ["error", "functions"],
127 |         "no-invalid-this": "off",
128 |         "no-iterator": "error",
129 |         "no-label-var": "error",
130 |         "no-labels": "error",
131 |         "no-lone-blocks": "error",
132 |         "no-lonely-if": "error",
133 |         "no-loop-func": "error",
134 |         "no-magic-numbers": "off",
135 |         "no-mixed-requires": "error",
136 |         "no-multi-assign": "off",
137 |         "no-multi-spaces": "error",
138 |         "no-multi-str": "error",
139 |         "no-multiple-empty-lines": "error",
140 |         "no-native-reassign": "error",
141 |         "no-negated-condition": "off",
142 |         "no-negated-in-lhs": "error",
143 |         "no-nested-ternary": "error",
144 |         "no-new": "error",
145 |         "no-new-func": "error",
146 |         "no-new-object": "error",
147 |         "no-new-require": "error",
148 |         "no-new-wrappers": "error",
149 |         "no-octal-escape": "error",
150 |         "no-param-reassign": "off",
151 |         "no-path-concat": "error",
152 | 
153 |         "no-plusplus": ["error", {
154 |             allowForLoopAfterthoughts: true,
155 |         }],
156 | 
157 |         "no-process-env": "off",
158 |         "no-process-exit": "error",
159 |         "no-proto": "error",
160 |         "no-prototype-builtins": "off",
161 |         "no-restricted-globals": "error",
162 |         "no-restricted-imports": "error",
163 |         "no-restricted-modules": "error",
164 |         "no-restricted-properties": "error",
165 |         "no-restricted-syntax": "error",
166 |         "no-return-assign": "error",
167 |         "no-return-await": "error",
168 |         "no-script-url": "error",
169 |         "no-self-compare": "error",
170 |         "no-sequences": "error",
171 |         "no-shadow": "off",
172 |         "no-shadow-restricted-names": "error",
173 |         "no-spaced-func": "error",
174 |         "no-sync": "error",
175 |         "no-tabs": "error",
176 |         "no-template-curly-in-string": "error",
177 |         "no-ternary": "off",
178 |         "no-throw-literal": "error",
179 |         "no-trailing-spaces": "error",
180 |         "no-undef-init": "error",
181 |         "no-undefined": "off",
182 |         "no-underscore-dangle": "off",
183 |         "no-unmodified-loop-condition": "error",
184 |         "no-unneeded-ternary": "error",
185 |         "no-unused-expressions": "error",
186 | 
187 |         "no-unused-vars": ["error", {
188 |             args: "none",
189 |         }],
190 | 
191 |         "no-use-before-define": "off",
192 |         "no-useless-call": "error",
193 |         "no-useless-computed-key": "error",
194 |         "no-useless-concat": "error",
195 |         "no-useless-constructor": "error",
196 |         "no-useless-escape": "off",
197 |         "no-useless-rename": "error",
198 |         "no-useless-return": "error",
199 |         "no-var": "off",
200 |         "no-void": "error",
201 |         "no-warning-comments": "error",
202 |         "no-whitespace-before-property": "error",
203 |         "no-with": "error",
204 |         "nonblock-statement-body-position": "error",
205 |         "object-curly-newline": "off",
206 |         "object-curly-spacing": ["error", "never"],
207 |         "object-property-newline": "off",
208 |         "object-shorthand": "off",
209 |         "one-var": "off",
210 |         "one-var-declaration-per-line": "error",
211 |         "operator-assignment": ["error", "always"],
212 |         "operator-linebreak": "off",
213 |         "padded-blocks": "off",
214 |         "prefer-arrow-callback": "off",
215 |         "prefer-const": "error",
216 | 
217 |         "prefer-destructuring": ["error", {
218 |             array: false,
219 |             object: false,
220 |         }],
221 | 
222 |         "prefer-numeric-literals": "error",
223 |         "prefer-promise-reject-errors": "error",
224 |         "prefer-reflect": "off",
225 |         "prefer-rest-params": "off",
226 |         "prefer-spread": "off",
227 |         "prefer-template": "off",
228 |         "quote-props": "off",
229 | 
230 |         quotes: ["error", "single", {
231 |             avoidEscape: true,
232 |         }],
233 | 
234 |         radix: "error",
235 |         "require-await": "error",
236 |         "require-jsdoc": "off",
237 |         "rest-spread-spacing": "error",
238 |         semi: "off",
239 | 
240 |         "semi-spacing": ["error", {
241 |             after: true,
242 |             before: false,
243 |         }],
244 | 
245 |         "sort-imports": "off",
246 |         "sort-keys": "off",
247 |         "sort-vars": "error",
248 |         "space-before-blocks": "error",
249 |         "space-before-function-paren": "off",
250 |         "space-in-parens": ["error", "never"],
251 |         "space-infix-ops": "error",
252 |         "space-unary-ops": "error",
253 |         "spaced-comment": ["error", "always"],
254 |         strict: "off",
255 |         "symbol-description": "error",
256 |         "template-curly-spacing": "error",
257 |         "template-tag-spacing": "error",
258 |         "unicode-bom": ["error", "never"],
259 |         "valid-jsdoc": "off",
260 |         "vars-on-top": "off",
261 |         "wrap-regex": "off",
262 |         "yield-star-spacing": "error",
263 |         yoda: ["error", "never"],
264 |     },
265 | }, ...compat.extends(
266 |     "eslint:recommended",
267 |     "plugin:@typescript-eslint/eslint-recommended",
268 |     "plugin:@typescript-eslint/recommended",
269 |     "plugin:prettier/recommended",
270 | ).map(config => ({
271 |     ...config,
272 |     files: ["**/*.ts"],
273 | })), {
274 |     files: ["**/*.ts"],
275 | 
276 |     plugins: {
277 |         "@typescript-eslint": typescriptEslint,
278 |         prettier,
279 |     },
280 | 
281 |     rules: {
282 |         "@typescript-eslint/no-use-before-define": 0,
283 |         "@typescript-eslint/no-empty-interface": 0,
284 |         "@typescript-eslint/no-unused-vars": 0,
285 |         "@typescript-eslint/triple-slash-reference": 0,
286 |         "@typescript-eslint/ban-ts-comment": "off",
287 |         "@typescript-eslint/no-empty-function": 0,
288 |         "@typescript-eslint/no-require-imports": 0,
289 | 
290 |         "@typescript-eslint/naming-convention": ["error", {
291 |             selector: "default",
292 |             format: ["camelCase", "UPPER_CASE", "PascalCase"],
293 |             leadingUnderscore: "allow",
294 |         }, {
295 |             selector: "property",
296 |             format: null,
297 |         }],
298 | 
299 |         "@typescript-eslint/no-explicit-any": 0,
300 |         "@typescript-eslint/explicit-function-return-type": "off",
301 |         "@typescript-eslint/no-this-alias": "off",
302 |         "@typescript-eslint/no-var-requires": 0,
303 |         "prefer-rest-params": "off",
304 |     },
305 | }, {
306 |     files: ["test/**/*.ts"],
307 | 
308 |     rules: {
309 |         "@typescript-eslint/explicit-function-return-type": "off",
310 |     },
311 | }, {
312 |     files: ["examples/cloudflare/**/*.ts", "examples/cloudflare/**/*.js", "examples/cloudflare/**/*.mjs"],
313 |     ignores: [],
314 |     rules: {
315 |         // Disable all rules for cloudflare examples
316 |         ...Object.fromEntries(
317 |             Object.keys(typescriptEslint.rules).map(rule => [`@typescript-eslint/${rule}`, "off"])
318 |         ),
319 |         // Disable all base rules
320 |         "no-unused-vars": "off",
321 |         "no-undef": "off",
322 |         "no-console": "off",
323 |         "require-await": "off",
324 |         "prettier/prettier": "off",
325 |         "func-style": "off",
326 |         "no-warning-comments": "off",
327 |         "no-constant-condition": "off",
328 |         // Add any other rules you want to disable
329 |     }
330 | }];
331 | 
```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/functions.py:
--------------------------------------------------------------------------------

```python
  1 | import stripe
  2 | from typing import Optional
  3 | from .configuration import Context
  4 | 
  5 | 
  6 | def create_customer(context: Context, name: str, email: Optional[str] = None):
  7 |     """
  8 |     Create a customer.
  9 | 
 10 |     Parameters:
 11 |         name (str): The name of the customer.
 12 |         email (str, optional): The email address of the customer.
 13 | 
 14 |     Returns:
 15 |         stripe.Customer: The created customer.
 16 |     """
 17 |     customer_data: dict = {"name": name}
 18 |     if email:
 19 |         customer_data["email"] = email
 20 |     if context.get("account") is not None:
 21 |         account = context.get("account")
 22 |         if account is not None:
 23 |             customer_data["stripe_account"] = account
 24 | 
 25 |     customer = stripe.Customer.create(**customer_data)
 26 |     return {"id": customer.id}
 27 | 
 28 | 
 29 | def list_customers(
 30 |     context: Context,
 31 |     email: Optional[str] = None,
 32 |     limit: Optional[int] = None,
 33 | ):
 34 |     """
 35 |     List Customers.
 36 | 
 37 |     Parameters:
 38 |         email (str, optional): The email address of the customer.
 39 |         limit (int, optional): The number of customers to return.
 40 | 
 41 |     Returns:
 42 |         stripe.ListObject: A list of customers.
 43 |     """
 44 |     customer_data: dict = {}
 45 |     if email:
 46 |         customer_data["email"] = email
 47 |     if limit:
 48 |         customer_data["limit"] = limit
 49 |     if context.get("account") is not None:
 50 |         account = context.get("account")
 51 |         if account is not None:
 52 |             customer_data["stripe_account"] = account
 53 | 
 54 |     customers = stripe.Customer.list(**customer_data)
 55 |     return [{"id": customer.id} for customer in customers.data]
 56 | 
 57 | 
 58 | def create_product(
 59 |     context: Context, name: str, description: Optional[str] = None
 60 | ):
 61 |     """
 62 |     Create a product.
 63 | 
 64 |     Parameters:
 65 |         name (str): The name of the product.
 66 |         description (str, optional): The description of the product.
 67 | 
 68 |     Returns:
 69 |         stripe.Product: The created product.
 70 |     """
 71 |     product_data: dict = {"name": name}
 72 |     if description:
 73 |         product_data["description"] = description
 74 |     if context.get("account") is not None:
 75 |         account = context.get("account")
 76 |         if account is not None:
 77 |             product_data["stripe_account"] = account
 78 | 
 79 |     return stripe.Product.create(**product_data)
 80 | 
 81 | 
 82 | def list_products(context: Context, limit: Optional[int] = None):
 83 |     """
 84 |     List Products.
 85 |     Parameters:
 86 |         limit (int, optional): The number of products to return.
 87 | 
 88 |     Returns:
 89 |         stripe.ListObject: A list of products.
 90 |     """
 91 |     product_data: dict = {}
 92 |     if limit:
 93 |         product_data["limit"] = limit
 94 |     if context.get("account") is not None:
 95 |         account = context.get("account")
 96 |         if account is not None:
 97 |             product_data["stripe_account"] = account
 98 | 
 99 |     return stripe.Product.list(**product_data).data
100 | 
101 | 
102 | def create_price(
103 |     context: Context, product: str, currency: str, unit_amount: int
104 | ):
105 |     """
106 |     Create a price.
107 | 
108 |     Parameters:
109 |         product (str): The ID of the product.
110 |         currency (str): The currency of the price.
111 |         unit_amount (int): The unit amount of the price.
112 | 
113 |     Returns:
114 |         stripe.Price: The created price.
115 |     """
116 |     price_data: dict = {
117 |         "product": product,
118 |         "currency": currency,
119 |         "unit_amount": unit_amount,
120 |     }
121 |     if context.get("account") is not None:
122 |         account = context.get("account")
123 |         if account is not None:
124 |             price_data["stripe_account"] = account
125 | 
126 |     return stripe.Price.create(**price_data)
127 | 
128 | 
129 | def list_prices(
130 |     context: Context,
131 |     product: Optional[str] = None,
132 |     limit: Optional[int] = None,
133 | ):
134 |     """
135 |     List Prices.
136 | 
137 |     Parameters:
138 |         product (str, optional): The ID of the product to list prices for.
139 |         limit (int, optional): The number of prices to return.
140 | 
141 |     Returns:
142 |         stripe.ListObject: A list of prices.
143 |     """
144 |     prices_data: dict = {}
145 |     if product:
146 |         prices_data["product"] = product
147 |     if limit:
148 |         prices_data["limit"] = limit
149 |     if context.get("account") is not None:
150 |         account = context.get("account")
151 |         if account is not None:
152 |             prices_data["stripe_account"] = account
153 | 
154 |     return stripe.Price.list(**prices_data).data
155 | 
156 | 
157 | def create_payment_link(context: Context, price: str, quantity: int, redirect_url: Optional[str] = None):
158 |     """
159 |     Create a payment link.
160 | 
161 |     Parameters:
162 |         price (str): The ID of the price.
163 |         quantity (int): The quantity of the product.
164 |         redirect_url (string, optional): The URL the customer will be redirected to after the purchase is complete.
165 | 
166 |     Returns:
167 |         stripe.PaymentLink: The created payment link.
168 |     """
169 |     payment_link_data: dict = {
170 |         "line_items": [{"price": price, "quantity": quantity}],
171 |     }
172 |     if context.get("account") is not None:
173 |         account = context.get("account")
174 |         if account is not None:
175 |             payment_link_data["stripe_account"] = account
176 | 
177 |     if redirect_url:
178 |         payment_link_data["after_completion"] = {"type": "redirect", "redirect": {"url": redirect_url}}
179 | 
180 |     payment_link = stripe.PaymentLink.create(**payment_link_data)
181 | 
182 |     return {"id": payment_link.id, "url": payment_link.url}
183 | 
184 | 
185 | def list_invoices(
186 |     context: Context,
187 |     customer: Optional[str] = None,
188 |     limit: Optional[int] = None,
189 | ):
190 |     """
191 |     List invoices.
192 | 
193 |     Parameters:
194 |         customer (str, optional): The ID of the customer.
195 |         limit (int, optional): The number of invoices to return.
196 | 
197 |     Returns:
198 |         stripe.ListObject: A list of invoices.
199 |     """
200 |     invoice_data: dict = {}
201 |     if customer:
202 |         invoice_data["customer"] = customer
203 |     if limit:
204 |         invoice_data["limit"] = limit
205 |     if context.get("account") is not None:
206 |         account = context.get("account")
207 |         if account is not None:
208 |             invoice_data["stripe_account"] = account
209 | 
210 |     return stripe.Invoice.list(**invoice_data).data
211 | 
212 | 
213 | def create_invoice(context: Context, customer: str, days_until_due: int = 30):
214 |     """
215 |     Create an invoice.
216 | 
217 |     Parameters:
218 |         customer (str): The ID of the customer.
219 |         days_until_due (int, optional): The number of days until the
220 |         invoice is due.
221 | 
222 |     Returns:
223 |         stripe.Invoice: The created invoice.
224 |     """
225 |     invoice_data: dict = {
226 |         "customer": customer,
227 |         "collection_method": "send_invoice",
228 |         "days_until_due": days_until_due,
229 |     }
230 |     if context.get("account") is not None:
231 |         account = context.get("account")
232 |         if account is not None:
233 |             invoice_data["stripe_account"] = account
234 | 
235 |     invoice = stripe.Invoice.create(**invoice_data)
236 | 
237 |     return {
238 |         "id": invoice.id,
239 |         "hosted_invoice_url": invoice.hosted_invoice_url,
240 |         "customer": invoice.customer,
241 |         "status": invoice.status,
242 |     }
243 | 
244 | 
245 | def create_invoice_item(
246 |     context: Context, customer: str, price: str, invoice: str
247 | ):
248 |     """
249 |     Create an invoice item.
250 | 
251 |     Parameters:
252 |         customer (str): The ID of the customer.
253 |         price (str): The ID of the price.
254 |         invoice (str): The ID of the invoice.
255 | 
256 |     Returns:
257 |         stripe.InvoiceItem: The created invoice item.
258 |     """
259 |     invoice_item_data: dict = {
260 |         "customer": customer,
261 |         "price": price,
262 |         "invoice": invoice,
263 |     }
264 |     if context.get("account") is not None:
265 |         account = context.get("account")
266 |         if account is not None:
267 |             invoice_item_data["stripe_account"] = account
268 | 
269 |     invoice_item = stripe.InvoiceItem.create(**invoice_item_data)
270 | 
271 |     return {"id": invoice_item.id, "invoice": invoice_item.invoice}
272 | 
273 | 
274 | def finalize_invoice(context: Context, invoice: str):
275 |     """
276 |     Finalize an invoice.
277 | 
278 |     Parameters:
279 |         invoice (str): The ID of the invoice.
280 | 
281 |     Returns:
282 |         stripe.Invoice: The finalized invoice.
283 |     """
284 |     invoice_data: dict = {"invoice": invoice}
285 |     if context.get("account") is not None:
286 |         account = context.get("account")
287 |         if account is not None:
288 |             invoice_data["stripe_account"] = account
289 | 
290 |     invoice_object = stripe.Invoice.finalize_invoice(**invoice_data)
291 | 
292 |     return {
293 |         "id": invoice_object.id,
294 |         "hosted_invoice_url": invoice_object.hosted_invoice_url,
295 |         "customer": invoice_object.customer,
296 |         "status": invoice_object.status,
297 |     }
298 | 
299 | 
300 | def retrieve_balance(
301 |     context: Context,
302 | ):
303 |     """
304 |     Retrieve the balance.
305 | 
306 |     Returns:
307 |         stripe.Balance: The balance.
308 |     """
309 |     balance_data: dict = {}
310 |     if context.get("account") is not None:
311 |         account = context.get("account")
312 |         if account is not None:
313 |             balance_data["stripe_account"] = account
314 | 
315 |     return stripe.Balance.retrieve(**balance_data)
316 | 
317 | 
318 | def create_refund(
319 |     context: Context, payment_intent: str, amount: Optional[int] = None
320 | ):
321 |     """
322 |     Create a refund.
323 | 
324 |     Parameters:
325 |         payment_intent (str): The ID of the payment intent.
326 |         amount (int, optional): The amount to refund in cents.
327 | 
328 |     Returns:
329 |         stripe.Refund: The created refund.
330 |     """
331 |     refund_data: dict = {
332 |         "payment_intent": payment_intent,
333 |     }
334 |     if amount:
335 |         refund_data["amount"] = amount
336 |         if context.get("account") is not None:
337 |             account = context.get("account")
338 |             if account is not None:
339 |                 refund_data["stripe_account"] = account
340 | 
341 |     return stripe.Refund.create(**refund_data)
342 | 
343 | def list_payment_intents(context: Context, customer: Optional[str] = None, limit: Optional[int] = None):
344 |     """
345 |     List payment intents.
346 | 
347 |     Parameters:
348 |         customer (str, optional): The ID of the customer to list payment intents for.
349 |         limit (int, optional): The number of payment intents to return.
350 | 
351 |     Returns:
352 |         stripe.ListObject: A list of payment intents.
353 |     """
354 |     payment_intent_data: dict = {}
355 |     if customer:
356 |         payment_intent_data["customer"] = customer
357 |     if limit:
358 |         payment_intent_data["limit"] = limit
359 |     if context.get("account") is not None:
360 |         account = context.get("account")
361 |         if account is not None:
362 |             payment_intent_data["stripe_account"] = account
363 | 
364 |     return stripe.PaymentIntent.list(**payment_intent_data).data
365 | 
366 | def create_billing_portal_session(context: Context, customer: str, return_url: Optional[str] = None):
367 |     """
368 |     Creates a session of the customer portal.
369 | 
370 |     Parameters:
371 |         customer (str): The ID of the customer to list payment intents for.
372 |         return_url (str, optional): The URL to return to after the session is complete.
373 | 
374 |     Returns:
375 |         stripe.BillingPortalSession: The created billing portal session.
376 |     """
377 |     billing_portal_session_data: dict = {
378 |         "customer": customer,
379 |     }
380 |     if return_url:
381 |         billing_portal_session_data["return_url"] = return_url
382 |     if context.get("account") is not None:
383 |         account = context.get("account")
384 |         if account is not None:
385 |             billing_portal_session_data["stripe_account"] = account
386 | 
387 |     session_object = stripe.billing_portal.Session.create(**billing_portal_session_data)
388 | 
389 |     return {
390 |         "id": session_object.id,
391 |         "customer": session_object.customer,
392 |         "url": session_object.url,
393 |     }
394 | 
```

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

```typescript
  1 | // From: https://github.com/cloudflare/ai/blob/main/demos/remote-mcp-server/src/utils.ts
  2 | 
  3 | // Helper to generate the layout
  4 | import {html, raw} from 'hono/html';
  5 | import type {HtmlEscapedString} from 'hono/utils/html';
  6 | import type {AuthRequest} from '@cloudflare/workers-oauth-provider';
  7 | 
  8 | // This file mainly exists as a dumping ground for uninteresting html and CSS
  9 | // to remove clutter and noise from the auth logic. You likely do not need
 10 | // anything from this file.
 11 | 
 12 | export const layout = (
 13 |   content: HtmlEscapedString | string,
 14 |   title: string
 15 | ) => html`
 16 |   <!DOCTYPE html>
 17 |   <html lang="en">
 18 |     <head>
 19 |       <meta charset="UTF-8" />
 20 |       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 21 |       <title>${title}</title>
 22 |       <script src="https://cdn.tailwindcss.com"></script>
 23 |       <script>
 24 |         tailwind.config = {
 25 |           theme: {
 26 |             extend: {
 27 |               colors: {
 28 |                 primary: '#3498db',
 29 |                 secondary: '#2ecc71',
 30 |                 accent: '#f39c12',
 31 |               },
 32 |               fontFamily: {
 33 |                 sans: ['Inter', 'system-ui', 'sans-serif'],
 34 |                 heading: ['Roboto', 'system-ui', 'sans-serif'],
 35 |               },
 36 |             },
 37 |           },
 38 |         };
 39 |       </script>
 40 |       <style>
 41 |         @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap');
 42 | 
 43 |         /* Custom styling for markdown content */
 44 |         .markdown h1 {
 45 |           font-size: 2.25rem;
 46 |           font-weight: 700;
 47 |           font-family: 'Roboto', system-ui, sans-serif;
 48 |           color: #1a202c;
 49 |           margin-bottom: 1rem;
 50 |           line-height: 1.2;
 51 |         }
 52 | 
 53 |         .markdown h2 {
 54 |           font-size: 1.5rem;
 55 |           font-weight: 600;
 56 |           font-family: 'Roboto', system-ui, sans-serif;
 57 |           color: #2d3748;
 58 |           margin-top: 1.5rem;
 59 |           margin-bottom: 0.75rem;
 60 |           line-height: 1.3;
 61 |         }
 62 | 
 63 |         .markdown h3 {
 64 |           font-size: 1.25rem;
 65 |           font-weight: 600;
 66 |           font-family: 'Roboto', system-ui, sans-serif;
 67 |           color: #2d3748;
 68 |           margin-top: 1.25rem;
 69 |           margin-bottom: 0.5rem;
 70 |         }
 71 | 
 72 |         .markdown p {
 73 |           font-size: 1.125rem;
 74 |           color: #4a5568;
 75 |           margin-bottom: 1rem;
 76 |           line-height: 1.6;
 77 |         }
 78 | 
 79 |         .markdown a {
 80 |           color: #3498db;
 81 |           font-weight: 500;
 82 |           text-decoration: none;
 83 |         }
 84 | 
 85 |         .markdown a:hover {
 86 |           text-decoration: underline;
 87 |         }
 88 | 
 89 |         .markdown blockquote {
 90 |           border-left: 4px solid #f39c12;
 91 |           padding-left: 1rem;
 92 |           padding-top: 0.75rem;
 93 |           padding-bottom: 0.75rem;
 94 |           margin-top: 1.5rem;
 95 |           margin-bottom: 1.5rem;
 96 |           background-color: #fffbeb;
 97 |           font-style: italic;
 98 |         }
 99 | 
100 |         .markdown blockquote p {
101 |           margin-bottom: 0.25rem;
102 |         }
103 | 
104 |         .markdown ul,
105 |         .markdown ol {
106 |           margin-top: 1rem;
107 |           margin-bottom: 1rem;
108 |           margin-left: 1.5rem;
109 |           font-size: 1.125rem;
110 |           color: #4a5568;
111 |         }
112 | 
113 |         .markdown li {
114 |           margin-bottom: 0.5rem;
115 |         }
116 | 
117 |         .markdown ul li {
118 |           list-style-type: disc;
119 |         }
120 | 
121 |         .markdown ol li {
122 |           list-style-type: decimal;
123 |         }
124 | 
125 |         .markdown pre {
126 |           background-color: #f7fafc;
127 |           padding: 1rem;
128 |           border-radius: 0.375rem;
129 |           margin-top: 1rem;
130 |           margin-bottom: 1rem;
131 |           overflow-x: auto;
132 |         }
133 | 
134 |         .markdown code {
135 |           font-family: monospace;
136 |           font-size: 0.875rem;
137 |           background-color: #f7fafc;
138 |           padding: 0.125rem 0.25rem;
139 |           border-radius: 0.25rem;
140 |         }
141 | 
142 |         .markdown pre code {
143 |           background-color: transparent;
144 |           padding: 0;
145 |         }
146 |       </style>
147 |     </head>
148 |     <body
149 |       class="bg-gray-50 text-gray-800 font-sans leading-relaxed flex flex-col min-h-screen"
150 |     >
151 |       <header class="bg-white shadow-sm mb-8">
152 |         <div
153 |           class="container mx-auto px-4 py-4 flex justify-between items-center"
154 |         >
155 |           <a
156 |             href="/"
157 |             class="text-xl font-heading font-bold text-primary hover:text-primary/80 transition-colors"
158 |             >MCP Remote Auth Demo</a
159 |           >
160 |         </div>
161 |       </header>
162 |       <main class="container mx-auto px-4 pb-12 flex-grow">${content}</main>
163 |       <footer class="bg-gray-100 py-6 mt-12">
164 |         <div class="container mx-auto px-4 text-center text-gray-600">
165 |           <p>
166 |             &copy; ${new Date().getFullYear()} MCP Remote Auth Demo. All rights
167 |             reserved.
168 |           </p>
169 |         </div>
170 |       </footer>
171 |     </body>
172 |   </html>
173 | `;
174 | 
175 | export const homeContent = async (req: Request): Promise<HtmlEscapedString> => {
176 |   return html`
177 |     <div class="max-w-4xl mx-auto markdown">Example Paid MCP Server</div>
178 |   `;
179 | };
180 | 
181 | export const renderLoggedInAuthorizeScreen = async (
182 |   oauthScopes: {name: string; description: string}[],
183 |   oauthReqInfo: AuthRequest
184 | ) => {
185 |   return html`
186 |     <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md">
187 |       <h1 class="text-2xl font-heading font-bold mb-6 text-gray-900">
188 |         Authorization Request
189 |       </h1>
190 | 
191 |       <div class="mb-8">
192 |         <h2 class="text-lg font-semibold mb-3 text-gray-800">
193 |           MCP Remote Auth Demo would like permission to:
194 |         </h2>
195 |         <ul class="space-y-2">
196 |           ${oauthScopes.map(
197 |             (scope) => html`
198 |               <li class="flex items-start">
199 |                 <span class="inline-block mr-2 mt-1 text-secondary">✓</span>
200 |                 <div>
201 |                   <p class="font-medium">${scope.name}</p>
202 |                   <p class="text-gray-600 text-sm">${scope.description}</p>
203 |                 </div>
204 |               </li>
205 |             `
206 |           )}
207 |         </ul>
208 |       </div>
209 |       <form action="/approve" method="POST" class="space-y-4">
210 |         <input
211 |           type="hidden"
212 |           name="oauthReqInfo"
213 |           value="${JSON.stringify(oauthReqInfo)}"
214 |         />
215 |         <input
216 |           name="email"
217 |           value="[email protected]"
218 |           required
219 |           placeholder="Enter email"
220 |           class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
221 |         />
222 |         <button
223 |           type="submit"
224 |           name="action"
225 |           value="approve"
226 |           class="w-full py-3 px-4 bg-secondary text-white rounded-md font-medium hover:bg-secondary/90 transition-colors"
227 |         >
228 |           Approve
229 |         </button>
230 |         <button
231 |           type="submit"
232 |           name="action"
233 |           value="reject"
234 |           class="w-full py-3 px-4 border border-gray-300 text-gray-700 rounded-md font-medium hover:bg-gray-50 transition-colors"
235 |         >
236 |           Reject
237 |         </button>
238 |       </form>
239 |     </div>
240 |   `;
241 | };
242 | 
243 | export const renderLoggedOutAuthorizeScreen = async (
244 |   oauthScopes: {name: string; description: string}[],
245 |   oauthReqInfo: AuthRequest
246 | ) => {
247 |   return html`
248 |     <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md">
249 |       <h1 class="text-2xl font-heading font-bold mb-6 text-gray-900">
250 |         Authorization Request
251 |       </h1>
252 | 
253 |       <div class="mb-8">
254 |         <h2 class="text-lg font-semibold mb-3 text-gray-800">
255 |           MCP Remote Auth Demo would like permission to:
256 |         </h2>
257 |         <ul class="space-y-2">
258 |           ${oauthScopes.map(
259 |             (scope) => html`
260 |               <li class="flex items-start">
261 |                 <span class="inline-block mr-2 mt-1 text-secondary">✓</span>
262 |                 <div>
263 |                   <p class="font-medium">${scope.name}</p>
264 |                   <p class="text-gray-600 text-sm">${scope.description}</p>
265 |                 </div>
266 |               </li>
267 |             `
268 |           )}
269 |         </ul>
270 |       </div>
271 |       <form action="/approve" method="POST" class="space-y-4">
272 |         <input
273 |           type="hidden"
274 |           name="oauthReqInfo"
275 |           value="${JSON.stringify(oauthReqInfo)}"
276 |         />
277 |         <div class="space-y-4">
278 |           <div>
279 |             <label
280 |               for="email"
281 |               class="block text-sm font-medium text-gray-700 mb-1"
282 |               >Email</label
283 |             >
284 |             <input
285 |               type="email"
286 |               id="email"
287 |               name="email"
288 |               required
289 |               class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
290 |             />
291 |           </div>
292 |           <div>
293 |             <label
294 |               for="password"
295 |               class="block text-sm font-medium text-gray-700 mb-1"
296 |               >Password</label
297 |             >
298 |             <input
299 |               type="password"
300 |               id="password"
301 |               name="password"
302 |               required
303 |               class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
304 |             />
305 |           </div>
306 |         </div>
307 |         <button
308 |           type="submit"
309 |           name="action"
310 |           value="login_approve"
311 |           class="w-full py-3 px-4 bg-primary text-white rounded-md font-medium hover:bg-primary/90 transition-colors"
312 |         >
313 |           Log in and Approve
314 |         </button>
315 |         <button
316 |           type="submit"
317 |           name="action"
318 |           value="reject"
319 |           class="w-full py-3 px-4 border border-gray-300 text-gray-700 rounded-md font-medium hover:bg-gray-50 transition-colors"
320 |         >
321 |           Reject
322 |         </button>
323 |       </form>
324 |     </div>
325 |   `;
326 | };
327 | 
328 | export const renderApproveContent = async (
329 |   message: string,
330 |   status: string,
331 |   redirectUrl: string
332 | ) => {
333 |   return html`
334 |     <div class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md text-center">
335 |       <div class="mb-4">
336 |         <span
337 |           class="inline-block p-3 ${status === 'success'
338 |             ? 'bg-green-100 text-green-800'
339 |             : 'bg-red-100 text-red-800'} rounded-full"
340 |         >
341 |           ${status === 'success' ? '✓' : '✗'}
342 |         </span>
343 |       </div>
344 |       <h1 class="text-2xl font-heading font-bold mb-4 text-gray-900">
345 |         ${message}
346 |       </h1>
347 |       <p class="mb-8 text-gray-600">
348 |         You will be redirected back to the application shortly.
349 |       </p>
350 |       ${raw(`
351 | 				<script>
352 | 					setTimeout(() => {
353 | 						window.location.href = "${redirectUrl}";
354 | 					}, 1000);
355 | 				</script>
356 | 			`)}
357 |     </div>
358 |   `;
359 | };
360 | 
361 | export const renderAuthorizationApprovedContent = async (
362 |   redirectUrl: string
363 | ) => {
364 |   return renderApproveContent(
365 |     'Authorization approved!',
366 |     'success',
367 |     redirectUrl
368 |   );
369 | };
370 | 
371 | export const renderAuthorizationRejectedContent = async (
372 |   redirectUrl: string
373 | ) => {
374 |   return renderApproveContent('Authorization rejected.', 'error', redirectUrl);
375 | };
376 | 
377 | export const parseApproveFormBody = async (body: {
378 |   [x: string]: string | File;
379 | }) => {
380 |   const action = body.action as string;
381 |   const email = body.email as string;
382 |   const password = body.password as string;
383 |   let oauthReqInfo: AuthRequest | null = null;
384 |   try {
385 |     oauthReqInfo = JSON.parse(body.oauthReqInfo as string) as AuthRequest;
386 |   } catch (e) {
387 |     oauthReqInfo = null;
388 |   }
389 | 
390 |   return {action, oauthReqInfo, email, password};
391 | };
392 | 
393 | export const renderPaymentSuccessContent =
394 |   async (): Promise<HtmlEscapedString> => {
395 |     return html`
396 |       <div
397 |         class="max-w-md mx-auto bg-white p-8 rounded-lg shadow-md text-center"
398 |       >
399 |         <h1 class="text-2xl font-heading font-bold mb-4 text-gray-900">
400 |           Payment Successful!
401 |         </h1>
402 |         <p class="mb-8 text-gray-600">
403 |           You can return to the MCP client now and rerun the tool.
404 |         </p>
405 |       </div>
406 |     `;
407 |   };
408 | 
```

--------------------------------------------------------------------------------
/python/tests/test_functions.py:
--------------------------------------------------------------------------------

```python
  1 | import unittest
  2 | import stripe
  3 | from unittest import mock
  4 | from stripe_agent_toolkit.functions import (
  5 |     create_customer,
  6 |     list_customers,
  7 |     create_product,
  8 |     list_products,
  9 |     create_price,
 10 |     list_prices,
 11 |     create_payment_link,
 12 |     list_invoices,
 13 |     create_invoice,
 14 |     create_invoice_item,
 15 |     finalize_invoice,
 16 |     retrieve_balance,
 17 |     create_refund,
 18 |     list_payment_intents,
 19 |     create_billing_portal_session,
 20 | )
 21 | 
 22 | 
 23 | class TestStripeFunctions(unittest.TestCase):
 24 |     def test_create_customer(self):
 25 |         with mock.patch("stripe.Customer.create") as mock_function:
 26 |             mock_customer = {"id": "cus_123"}
 27 |             mock_function.return_value = stripe.Customer.construct_from(
 28 |                 mock_customer, "sk_test_123"
 29 |             )
 30 | 
 31 |             result = create_customer(
 32 |                 context={}, name="Test User", email="[email protected]"
 33 |             )
 34 | 
 35 |             mock_function.assert_called_with(
 36 |                 name="Test User", email="[email protected]"
 37 |             )
 38 | 
 39 |             self.assertEqual(result, {"id": mock_customer["id"]})
 40 | 
 41 |     def test_create_customer_with_context(self):
 42 |         with mock.patch("stripe.Customer.create") as mock_function:
 43 |             mock_customer = {"id": "cus_123"}
 44 |             mock_function.return_value = stripe.Customer.construct_from(
 45 |                 mock_customer, "sk_test_123"
 46 |             )
 47 | 
 48 |             result = create_customer(
 49 |                 context={"account": "acct_123"},
 50 |                 name="Test User",
 51 |                 email="[email protected]",
 52 |             )
 53 | 
 54 |             mock_function.assert_called_with(
 55 |                 name="Test User",
 56 |                 email="[email protected]",
 57 |                 stripe_account="acct_123",
 58 |             )
 59 | 
 60 |             self.assertEqual(result, {"id": mock_customer["id"]})
 61 | 
 62 |     def test_list_customers(self):
 63 |         with mock.patch("stripe.Customer.list") as mock_function:
 64 |             mock_customers = [{"id": "cus_123"}, {"id": "cus_456"}]
 65 | 
 66 |             mock_function.return_value = stripe.ListObject.construct_from(
 67 |                 {
 68 |                     "object": "list",
 69 |                     "data": [
 70 |                         stripe.Customer.construct_from(
 71 |                             {
 72 |                                 "id": "cus_123",
 73 |                                 "email": "[email protected]",
 74 |                                 "name": "Customer One",
 75 |                             },
 76 |                             "sk_test_123",
 77 |                         ),
 78 |                         stripe.Customer.construct_from(
 79 |                             {
 80 |                                 "id": "cus_456",
 81 |                                 "email": "[email protected]",
 82 |                                 "name": "Customer Two",
 83 |                             },
 84 |                             "sk_test_123",
 85 |                         ),
 86 |                     ],
 87 |                     "has_more": False,
 88 |                     "url": "/v1/customers",
 89 |                 },
 90 |                 "sk_test_123",
 91 |             )
 92 | 
 93 |             result = list_customers(context={})
 94 | 
 95 |             mock_function.assert_called_with()
 96 | 
 97 |             self.assertEqual(result, mock_customers)
 98 | 
 99 |     def test_list_customers_with_context(self):
100 |         with mock.patch("stripe.Customer.list") as mock_function:
101 |             mock_customers = [{"id": "cus_123"}, {"id": "cus_456"}]
102 | 
103 |             mock_function.return_value = stripe.ListObject.construct_from(
104 |                 {
105 |                     "object": "list",
106 |                     "data": [
107 |                         stripe.Customer.construct_from(
108 |                             {
109 |                                 "id": "cus_123",
110 |                                 "email": "[email protected]",
111 |                                 "name": "Customer One",
112 |                             },
113 |                             "sk_test_123",
114 |                         ),
115 |                         stripe.Customer.construct_from(
116 |                             {
117 |                                 "id": "cus_456",
118 |                                 "email": "[email protected]",
119 |                                 "name": "Customer Two",
120 |                             },
121 |                             "sk_test_123",
122 |                         ),
123 |                     ],
124 |                     "has_more": False,
125 |                     "url": "/v1/customers",
126 |                 },
127 |                 "sk_test_123",
128 |             )
129 | 
130 |             result = list_customers(context={"account": "acct_123"})
131 | 
132 |             mock_function.assert_called_with(
133 |                 stripe_account="acct_123",
134 |             )
135 | 
136 |             self.assertEqual(result, mock_customers)
137 | 
138 |     def test_create_product(self):
139 |         with mock.patch("stripe.Product.create") as mock_function:
140 |             mock_product = {"id": "prod_123"}
141 |             mock_function.return_value = stripe.Product.construct_from(
142 |                 mock_product, "sk_test_123"
143 |             )
144 | 
145 |             result = create_product(context={}, name="Test Product")
146 | 
147 |             mock_function.assert_called_with(
148 |                 name="Test Product",
149 |             )
150 | 
151 |             self.assertEqual(result, {"id": mock_product["id"]})
152 | 
153 |     def test_create_product_with_context(self):
154 |         with mock.patch("stripe.Product.create") as mock_function:
155 |             mock_product = {"id": "prod_123"}
156 |             mock_function.return_value = stripe.Product.construct_from(
157 |                 mock_product, "sk_test_123"
158 |             )
159 | 
160 |             result = create_product(
161 |                 context={"account": "acct_123"}, name="Test Product"
162 |             )
163 | 
164 |             mock_function.assert_called_with(
165 |                 name="Test Product", stripe_account="acct_123"
166 |             )
167 | 
168 |             self.assertEqual(result, {"id": mock_product["id"]})
169 | 
170 |     def test_list_products(self):
171 |         with mock.patch("stripe.Product.list") as mock_function:
172 |             mock_products = [
173 |                 {"id": "prod_123", "name": "Product One"},
174 |                 {"id": "prod_456", "name": "Product Two"},
175 |             ]
176 | 
177 |             mock_function.return_value = stripe.ListObject.construct_from(
178 |                 {
179 |                     "object": "list",
180 |                     "data": [
181 |                         stripe.Product.construct_from(
182 |                             {
183 |                                 "id": "prod_123",
184 |                                 "name": "Product One",
185 |                             },
186 |                             "sk_test_123",
187 |                         ),
188 |                         stripe.Product.construct_from(
189 |                             {
190 |                                 "id": "prod_456",
191 |                                 "name": "Product Two",
192 |                             },
193 |                             "sk_test_123",
194 |                         ),
195 |                     ],
196 |                     "has_more": False,
197 |                     "url": "/v1/products",
198 |                 },
199 |                 "sk_test_123",
200 |             )
201 | 
202 |             result = list_products(context={})
203 | 
204 |             mock_function.assert_called_with()
205 | 
206 |             self.assertEqual(result, mock_products)
207 | 
208 |     def test_create_price(self):
209 |         with mock.patch("stripe.Price.create") as mock_function:
210 |             mock_price = {"id": "price_123"}
211 |             mock_function.return_value = stripe.Price.construct_from(
212 |                 mock_price, "sk_test_123"
213 |             )
214 | 
215 |             result = create_price(
216 |                 context={},
217 |                 product="prod_123",
218 |                 currency="usd",
219 |                 unit_amount=1000,
220 |             )
221 | 
222 |             mock_function.assert_called_with(
223 |                 product="prod_123",
224 |                 currency="usd",
225 |                 unit_amount=1000,
226 |             )
227 | 
228 |             self.assertEqual(result, {"id": mock_price["id"]})
229 | 
230 |     def test_create_price_with_context(self):
231 |         with mock.patch("stripe.Price.create") as mock_function:
232 |             mock_price = {"id": "price_123"}
233 |             mock_function.return_value = stripe.Price.construct_from(
234 |                 mock_price, "sk_test_123"
235 |             )
236 | 
237 |             result = create_price(
238 |                 context={"account": "acct_123"},
239 |                 product="prod_123",
240 |                 currency="usd",
241 |                 unit_amount=1000,
242 |             )
243 | 
244 |             mock_function.assert_called_with(
245 |                 product="prod_123",
246 |                 currency="usd",
247 |                 unit_amount=1000,
248 |                 stripe_account="acct_123",
249 |             )
250 | 
251 |             self.assertEqual(result, {"id": mock_price["id"]})
252 | 
253 |     def test_list_prices(self):
254 |         with mock.patch("stripe.Price.list") as mock_function:
255 |             mock_prices = [
256 |                 {"id": "price_123", "product": "prod_123"},
257 |                 {"id": "price_456", "product": "prod_456"},
258 |             ]
259 | 
260 |             mock_function.return_value = stripe.ListObject.construct_from(
261 |                 {
262 |                     "object": "list",
263 |                     "data": [
264 |                         stripe.Price.construct_from(
265 |                             {
266 |                                 "id": "price_123",
267 |                                 "product": "prod_123",
268 |                             },
269 |                             "sk_test_123",
270 |                         ),
271 |                         stripe.Price.construct_from(
272 |                             {
273 |                                 "id": "price_456",
274 |                                 "product": "prod_456",
275 |                             },
276 |                             "sk_test_123",
277 |                         ),
278 |                     ],
279 |                     "has_more": False,
280 |                     "url": "/v1/prices",
281 |                 },
282 |                 "sk_test_123",
283 |             )
284 | 
285 |             result = list_prices({})
286 | 
287 |             mock_function.assert_called_with()
288 | 
289 |             self.assertEqual(result, mock_prices)
290 | 
291 |     def test_list_prices_with_context(self):
292 |         with mock.patch("stripe.Price.list") as mock_function:
293 |             mock_prices = [
294 |                 {"id": "price_123", "product": "prod_123"},
295 |                 {"id": "price_456", "product": "prod_456"},
296 |             ]
297 | 
298 |             mock_function.return_value = stripe.ListObject.construct_from(
299 |                 {
300 |                     "object": "list",
301 |                     "data": [
302 |                         stripe.Price.construct_from(
303 |                             {
304 |                                 "id": "price_123",
305 |                                 "product": "prod_123",
306 |                             },
307 |                             "sk_test_123",
308 |                         ),
309 |                         stripe.Price.construct_from(
310 |                             {
311 |                                 "id": "price_456",
312 |                                 "product": "prod_456",
313 |                             },
314 |                             "sk_test_123",
315 |                         ),
316 |                     ],
317 |                     "has_more": False,
318 |                     "url": "/v1/prices",
319 |                 },
320 |                 "sk_test_123",
321 |             )
322 | 
323 |             result = list_prices({"account": "acct_123"})
324 | 
325 |             mock_function.assert_called_with(stripe_account="acct_123")
326 | 
327 |             self.assertEqual(result, mock_prices)
328 | 
329 |     def test_create_payment_link(self):
330 |         with mock.patch("stripe.PaymentLink.create") as mock_function:
331 |             mock_payment_link = {"id": "pl_123", "url": "https://example.com"}
332 |             mock_function.return_value = stripe.PaymentLink.construct_from(
333 |                 mock_payment_link, "sk_test_123"
334 |             )
335 | 
336 |             result = create_payment_link(
337 |                 context={}, price="price_123", quantity=1
338 |             )
339 | 
340 |             mock_function.assert_called_with(
341 |                 line_items=[{"price": "price_123", "quantity": 1}],
342 |             )
343 | 
344 |             self.assertEqual(result, mock_payment_link)
345 | 
346 |     def test_create_payment_link_with_redirect_url(self):
347 |         with mock.patch("stripe.PaymentLink.create") as mock_function:
348 |             mock_payment_link = {"id": "pl_123", "url": "https://example.com"}
349 |             mock_function.return_value = stripe.PaymentLink.construct_from(
350 |                 mock_payment_link, "sk_test_123"
351 |             )
352 | 
353 |             result = create_payment_link(
354 |                 context={}, price="price_123", quantity=1, redirect_url="https://example.com"
355 |             )
356 | 
357 |             mock_function.assert_called_with(
358 |                 line_items=[{"price": "price_123", "quantity": 1, }],
359 |                 after_completion={"type": "redirect", "redirect": {"url": "https://example.com"}}
360 |             )
361 | 
362 |             self.assertEqual(result, mock_payment_link)
363 | 
364 |     def test_create_payment_link_with_context(self):
365 |         with mock.patch("stripe.PaymentLink.create") as mock_function:
366 |             mock_payment_link = {"id": "pl_123", "url": "https://example.com"}
367 |             mock_function.return_value = stripe.PaymentLink.construct_from(
368 |                 mock_payment_link, "sk_test_123"
369 |             )
370 | 
371 |             result = create_payment_link(
372 |                 context={"account": "acct_123"}, price="price_123", quantity=1
373 |             )
374 | 
375 |             mock_function.assert_called_with(
376 |                 line_items=[{"price": "price_123", "quantity": 1}],
377 |                 stripe_account="acct_123",
378 |             )
379 | 
380 |             self.assertEqual(result, mock_payment_link)
381 | 
382 |     def test_list_invoices(self):
383 |         with mock.patch("stripe.Invoice.list") as mock_function:
384 |             mock_invoice = {
385 |                 "id": "in_123",
386 |                 "hosted_invoice_url": "https://example.com",
387 |                 "customer": "cus_123",
388 |                 "status": "open",
389 |             }
390 |             mock_invoices = {
391 |                 "object": "list",
392 |                 "data": [
393 |                     stripe.Invoice.construct_from(
394 |                         mock_invoice,
395 |                         "sk_test_123",
396 |                     ),
397 |                 ],
398 |                 "has_more": False,
399 |                 "url": "/v1/invoices",
400 |             }
401 | 
402 |             mock_function.return_value = stripe.Invoice.construct_from(
403 |                 mock_invoices, "sk_test_123"
404 |             )
405 | 
406 |             result = list_invoices(context={})
407 | 
408 |             mock_function.assert_called_with()
409 | 
410 |             self.assertEqual(
411 |                 result,
412 |                 [
413 |                     {
414 |                         "id": mock_invoice["id"],
415 |                         "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
416 |                         "customer": mock_invoice["customer"],
417 |                         "status": mock_invoice["status"],
418 |                     }
419 |                 ],
420 |             )
421 | 
422 |     def test_list_invoices_with_customer(self):
423 |         with mock.patch("stripe.Invoice.list") as mock_function:
424 |             mock_invoice = {
425 |                 "id": "in_123",
426 |                 "hosted_invoice_url": "https://example.com",
427 |                 "customer": "cus_123",
428 |                 "status": "open",
429 |             }
430 |             mock_invoices = {
431 |                 "object": "list",
432 |                 "data": [
433 |                     stripe.Invoice.construct_from(
434 |                         mock_invoice,
435 |                         "sk_test_123",
436 |                     ),
437 |                 ],
438 |                 "has_more": False,
439 |                 "url": "/v1/invoices",
440 |             }
441 | 
442 |             mock_function.return_value = stripe.Invoice.construct_from(
443 |                 mock_invoices, "sk_test_123"
444 |             )
445 | 
446 |             result = list_invoices(context={}, customer="cus_123")
447 | 
448 |             mock_function.assert_called_with(
449 |                 customer="cus_123",
450 |             )
451 | 
452 |             self.assertEqual(
453 |                 result,
454 |                 [
455 |                     {
456 |                         "id": mock_invoice["id"],
457 |                         "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
458 |                         "customer": mock_invoice["customer"],
459 |                         "status": mock_invoice["status"],
460 |                     }
461 |                 ],
462 |             )
463 | 
464 |     def test_list_invoices_with_customer_and_limit(self):
465 |         with mock.patch("stripe.Invoice.list") as mock_function:
466 |             mock_invoice = {
467 |                 "id": "in_123",
468 |                 "hosted_invoice_url": "https://example.com",
469 |                 "customer": "cus_123",
470 |                 "status": "open",
471 |             }
472 |             mock_invoices = {
473 |                 "object": "list",
474 |                 "data": [
475 |                     stripe.Invoice.construct_from(
476 |                         mock_invoice,
477 |                         "sk_test_123",
478 |                     ),
479 |                 ],
480 |                 "has_more": False,
481 |                 "url": "/v1/invoices",
482 |             }
483 | 
484 |             mock_function.return_value = stripe.Invoice.construct_from(
485 |                 mock_invoices, "sk_test_123"
486 |             )
487 | 
488 |             result = list_invoices(context={}, customer="cus_123", limit=100)
489 | 
490 |             mock_function.assert_called_with(
491 |                 customer="cus_123",
492 |                 limit=100,
493 |             )
494 | 
495 |             self.assertEqual(
496 |                 result,
497 |                 [
498 |                     {
499 |                         "id": mock_invoice["id"],
500 |                         "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
501 |                         "customer": mock_invoice["customer"],
502 |                         "status": mock_invoice["status"],
503 |                     }
504 |                 ],
505 |             )
506 | 
507 |     def test_list_invoices_with_context(self):
508 |         with mock.patch("stripe.Invoice.list") as mock_function:
509 |             mock_invoice = {
510 |                 "id": "in_123",
511 |                 "hosted_invoice_url": "https://example.com",
512 |                 "customer": "cus_123",
513 |                 "status": "open",
514 |             }
515 |             mock_invoices = {
516 |                 "object": "list",
517 |                 "data": [
518 |                     stripe.Invoice.construct_from(
519 |                         mock_invoice,
520 |                         "sk_test_123",
521 |                     ),
522 |                 ],
523 |                 "has_more": False,
524 |                 "url": "/v1/invoices",
525 |             }
526 | 
527 |             mock_function.return_value = stripe.Invoice.construct_from(
528 |                 mock_invoices, "sk_test_123"
529 |             )
530 | 
531 |             result = list_invoices(context={"account": "acct_123"}, customer="cus_123")
532 | 
533 |             mock_function.assert_called_with(
534 |                 customer="cus_123",
535 |                 stripe_account="acct_123",
536 |             )
537 | 
538 |             self.assertEqual(
539 |                 result,
540 |                 [
541 |                     {
542 |                         "id": mock_invoice["id"],
543 |                         "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
544 |                         "customer": mock_invoice["customer"],
545 |                         "status": mock_invoice["status"],
546 |                     }
547 |                 ],
548 |             )
549 | 
550 |     def test_create_invoice(self):
551 |         with mock.patch("stripe.Invoice.create") as mock_function:
552 |             mock_invoice = {
553 |                 "id": "in_123",
554 |                 "hosted_invoice_url": "https://example.com",
555 |                 "customer": "cus_123",
556 |                 "status": "open",
557 |             }
558 | 
559 |             mock_function.return_value = stripe.Invoice.construct_from(
560 |                 mock_invoice, "sk_test_123"
561 |             )
562 | 
563 |             result = create_invoice(context={}, customer="cus_123")
564 | 
565 |             mock_function.assert_called_with(
566 |                 customer="cus_123",
567 |                 collection_method="send_invoice",
568 |                 days_until_due=30,
569 |             )
570 | 
571 |             self.assertEqual(
572 |                 result,
573 |                 {
574 |                     "id": mock_invoice["id"],
575 |                     "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
576 |                     "customer": mock_invoice["customer"],
577 |                     "status": mock_invoice["status"],
578 |                 },
579 |             )
580 | 
581 |     def test_create_invoice_with_context(self):
582 |         with mock.patch("stripe.Invoice.create") as mock_function:
583 |             mock_invoice = {
584 |                 "id": "in_123",
585 |                 "hosted_invoice_url": "https://example.com",
586 |                 "customer": "cus_123",
587 |                 "status": "open",
588 |             }
589 | 
590 |             mock_function.return_value = stripe.Invoice.construct_from(
591 |                 mock_invoice, "sk_test_123"
592 |             )
593 | 
594 |             result = create_invoice(
595 |                 context={"account": "acct_123"}, customer="cus_123"
596 |             )
597 | 
598 |             mock_function.assert_called_with(
599 |                 customer="cus_123",
600 |                 collection_method="send_invoice",
601 |                 days_until_due=30,
602 |                 stripe_account="acct_123",
603 |             )
604 | 
605 |             self.assertEqual(
606 |                 result,
607 |                 {
608 |                     "id": mock_invoice["id"],
609 |                     "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
610 |                     "customer": mock_invoice["customer"],
611 |                     "status": mock_invoice["status"],
612 |                 },
613 |             )
614 | 
615 |     def test_create_invoice_item(self):
616 |         with mock.patch("stripe.InvoiceItem.create") as mock_function:
617 |             mock_invoice_item = {"id": "ii_123", "invoice": "in_123"}
618 |             mock_function.return_value = stripe.InvoiceItem.construct_from(
619 |                 mock_invoice_item, "sk_test_123"
620 |             )
621 | 
622 |             result = create_invoice_item(
623 |                 context={},
624 |                 customer="cus_123",
625 |                 price="price_123",
626 |                 invoice="in_123",
627 |             )
628 | 
629 |             mock_function.assert_called_with(
630 |                 customer="cus_123", price="price_123", invoice="in_123"
631 |             )
632 | 
633 |             self.assertEqual(
634 |                 result,
635 |                 {
636 |                     "id": mock_invoice_item["id"],
637 |                     "invoice": mock_invoice_item["invoice"],
638 |                 },
639 |             )
640 | 
641 |     def test_create_invoice_item_with_context(self):
642 |         with mock.patch("stripe.InvoiceItem.create") as mock_function:
643 |             mock_invoice_item = {"id": "ii_123", "invoice": "in_123"}
644 |             mock_function.return_value = stripe.InvoiceItem.construct_from(
645 |                 mock_invoice_item, "sk_test_123"
646 |             )
647 | 
648 |             result = create_invoice_item(
649 |                 context={"account": "acct_123"},
650 |                 customer="cus_123",
651 |                 price="price_123",
652 |                 invoice="in_123",
653 |             )
654 | 
655 |             mock_function.assert_called_with(
656 |                 customer="cus_123",
657 |                 price="price_123",
658 |                 invoice="in_123",
659 |                 stripe_account="acct_123",
660 |             )
661 | 
662 |             self.assertEqual(
663 |                 result,
664 |                 {
665 |                     "id": mock_invoice_item["id"],
666 |                     "invoice": mock_invoice_item["invoice"],
667 |                 },
668 |             )
669 | 
670 |     def test_finalize_invoice(self):
671 |         with mock.patch("stripe.Invoice.finalize_invoice") as mock_function:
672 |             mock_invoice = {
673 |                 "id": "in_123",
674 |                 "hosted_invoice_url": "https://example.com",
675 |                 "customer": "cus_123",
676 |                 "status": "open",
677 |             }
678 | 
679 |             mock_function.return_value = stripe.Invoice.construct_from(
680 |                 mock_invoice, "sk_test_123"
681 |             )
682 | 
683 |             result = finalize_invoice(context={}, invoice="in_123")
684 | 
685 |             mock_function.assert_called_with(invoice="in_123")
686 | 
687 |             self.assertEqual(
688 |                 result,
689 |                 {
690 |                     "id": mock_invoice["id"],
691 |                     "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
692 |                     "customer": mock_invoice["customer"],
693 |                     "status": mock_invoice["status"],
694 |                 },
695 |             )
696 | 
697 |     def test_finalize_invoice_with_context(self):
698 |         with mock.patch("stripe.Invoice.finalize_invoice") as mock_function:
699 |             mock_invoice = {
700 |                 "id": "in_123",
701 |                 "hosted_invoice_url": "https://example.com",
702 |                 "customer": "cus_123",
703 |                 "status": "open",
704 |             }
705 | 
706 |             mock_function.return_value = stripe.Invoice.construct_from(
707 |                 mock_invoice, "sk_test_123"
708 |             )
709 | 
710 |             result = finalize_invoice(
711 |                 context={"account": "acct_123"}, invoice="in_123"
712 |             )
713 | 
714 |             mock_function.assert_called_with(
715 |                 invoice="in_123", stripe_account="acct_123"
716 |             )
717 | 
718 |             self.assertEqual(
719 |                 result,
720 |                 {
721 |                     "id": mock_invoice["id"],
722 |                     "hosted_invoice_url": mock_invoice["hosted_invoice_url"],
723 |                     "customer": mock_invoice["customer"],
724 |                     "status": mock_invoice["status"],
725 |                 },
726 |             )
727 | 
728 |     def test_retrieve_balance(self):
729 |         with mock.patch("stripe.Balance.retrieve") as mock_function:
730 |             mock_balance = {"available": [{"amount": 1000, "currency": "usd"}]}
731 | 
732 |             mock_function.return_value = stripe.Balance.construct_from(
733 |                 mock_balance, "sk_test_123"
734 |             )
735 | 
736 |             result = retrieve_balance(context={})
737 | 
738 |             mock_function.assert_called_with()
739 | 
740 |             self.assertEqual(result, mock_balance)
741 | 
742 |     def test_retrieve_balance_with_context(self):
743 |         with mock.patch("stripe.Balance.retrieve") as mock_function:
744 |             mock_balance = {"available": [{"amount": 1000, "currency": "usd"}]}
745 | 
746 |             mock_function.return_value = stripe.Balance.construct_from(
747 |                 mock_balance, "sk_test_123"
748 |             )
749 | 
750 |             result = retrieve_balance(context={"account": "acct_123"})
751 | 
752 |             mock_function.assert_called_with(stripe_account="acct_123")
753 | 
754 |             self.assertEqual(result, mock_balance)
755 | 
756 |     def test_create_refund(self):
757 |         with mock.patch("stripe.Refund.create") as mock_function:
758 |             mock_refund = {"id": "re_123"}
759 |             mock_function.return_value = stripe.Refund.construct_from(
760 |                 mock_refund, "sk_test_123"
761 |             )
762 | 
763 |             result = create_refund(context={}, payment_intent="pi_123")
764 | 
765 |             mock_function.assert_called_with(payment_intent="pi_123")
766 | 
767 |             self.assertEqual(result, {"id": mock_refund["id"]})
768 | 
769 |     def test_create_partial_refund(self):
770 |         with mock.patch("stripe.Refund.create") as mock_function:
771 |             mock_refund = {"id": "re_123"}
772 |             mock_function.return_value = stripe.Refund.construct_from(
773 |                 mock_refund, "sk_test_123"
774 |             )
775 | 
776 |             result = create_refund(
777 |                 context={}, payment_intent="pi_123", amount=1000
778 |             )
779 | 
780 |             mock_function.assert_called_with(
781 |                 payment_intent="pi_123", amount=1000
782 |             )
783 | 
784 |             self.assertEqual(result, {"id": mock_refund["id"]})
785 | 
786 |     def test_create_refund_with_context(self):
787 |         with mock.patch("stripe.Refund.create") as mock_function:
788 |             mock_refund = {"id": "re_123"}
789 |             mock_function.return_value = stripe.Refund.construct_from(
790 |                 mock_refund, "sk_test_123"
791 |             )
792 | 
793 |             result = create_refund(
794 |                 context={"account": "acct_123"},
795 |                 payment_intent="pi_123",
796 |                 amount=1000,
797 |             )
798 | 
799 |             mock_function.assert_called_with(
800 |                 payment_intent="pi_123", amount=1000, stripe_account="acct_123"
801 |             )
802 | 
803 |             self.assertEqual(result, {"id": mock_refund["id"]})
804 | 
805 |     def test_list_payment_intents(self):
806 |         with mock.patch("stripe.PaymentIntent.list") as mock_function:
807 |             mock_payment_intents = [{"id": "pi_123"}, {"id": "pi_456"}]
808 |             mock_function.return_value = stripe.ListObject.construct_from(
809 |                 {"data": mock_payment_intents}, "sk_test_123"
810 |             )
811 | 
812 |             result = list_payment_intents(context={})
813 | 
814 |             mock_function.assert_called_with()
815 | 
816 |             self.assertEqual(result, mock_payment_intents)
817 | 
818 |     def test_list_payment_intents_with_context(self):
819 |         with mock.patch("stripe.PaymentIntent.list") as mock_function:
820 |             mock_payment_intents = [{"id": "pi_123"}, {"id": "pi_456"}]
821 |             mock_function.return_value = stripe.ListObject.construct_from(
822 |                 {"data": mock_payment_intents}, "sk_test_123"
823 |             )
824 | 
825 |             result = list_payment_intents(context={"account": "acct_123"})
826 | 
827 |             mock_function.assert_called_with(stripe_account="acct_123")
828 | 
829 |             self.assertEqual(result, mock_payment_intents)
830 | 
831 | 
832 |     def test_create_billing_portal_session(self):
833 |         with mock.patch("stripe.billing_portal.Session.create") as mock_function:
834 |             mock_billing_portal_session = {
835 |                 "id": "bps_123",
836 |                 "url": "https://example.com",
837 |                 "customer": "cus_123",
838 |                 "configuration": "bpc_123",
839 |             }
840 |             mock_function.return_value = stripe.billing_portal.Session.construct_from(
841 |                 mock_billing_portal_session, "sk_test_123"
842 |             )
843 | 
844 |             result = create_billing_portal_session(context={}, customer="cus_123")
845 | 
846 |             mock_function.assert_called_with(customer="cus_123")
847 | 
848 |             self.assertEqual(result, {
849 |                 "id": mock_billing_portal_session["id"],
850 |                 "url": mock_billing_portal_session["url"],
851 |                 "customer": mock_billing_portal_session["customer"],
852 |             })
853 | 
854 |     def test_create_billing_portal_session_with_return_url(self):
855 |         with mock.patch("stripe.billing_portal.Session.create") as mock_function:
856 |             mock_billing_portal_session = {
857 |                 "id": "bps_123",
858 |                 "url": "https://example.com",
859 |                 "customer": "cus_123",
860 |                 "configuration": "bpc_123",
861 |             }
862 |             mock_function.return_value = stripe.billing_portal.Session.construct_from(
863 |                 mock_billing_portal_session, "sk_test_123"
864 |             )
865 | 
866 |             result = create_billing_portal_session(
867 |                 context={},
868 |                 customer="cus_123",
869 |                 return_url="http://example.com"
870 |             )
871 | 
872 |             mock_function.assert_called_with(
873 |                 customer="cus_123",
874 |                 return_url="http://example.com",
875 |             )
876 | 
877 |             self.assertEqual(result, {
878 |                 "id": mock_billing_portal_session["id"],
879 |                 "url": mock_billing_portal_session["url"],
880 |                 "customer": mock_billing_portal_session["customer"],
881 |             })
882 | 
883 |     def test_create_billing_portal_session_with_context(self):
884 |         with mock.patch("stripe.billing_portal.Session.create") as mock_function:
885 |             mock_billing_portal_session = {
886 |                 "id": "bps_123",
887 |                 "url": "https://example.com",
888 |                 "customer": "cus_123",
889 |                 "configuration": "bpc_123",
890 |             }
891 |             mock_function.return_value = stripe.billing_portal.Session.construct_from(
892 |                 mock_billing_portal_session, "sk_test_123"
893 |             )
894 | 
895 |             result = create_billing_portal_session(
896 |                 context={"account": "acct_123"},
897 |                 customer="cus_123",
898 |                 return_url="http://example.com"
899 |             )
900 | 
901 |             mock_function.assert_called_with(
902 |                 customer="cus_123",
903 |                 return_url="http://example.com",
904 |                 stripe_account="acct_123"
905 |             )
906 | 
907 |             self.assertEqual(result, {
908 |                 "id": mock_billing_portal_session["id"],
909 |                 "url": mock_billing_portal_session["url"],
910 |                 "customer": mock_billing_portal_session["customer"],
911 |             })
912 | 
913 | if __name__ == "__main__":
914 |     unittest.main()
915 | 
```
Page 3/4FirstPrevNextLast