#
tokens: 49946/50000 162/192 files (page 1/3)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 3. Use http://codebase.md/stripe/agent-toolkit?lines=false&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

--------------------------------------------------------------------------------
/modelcontextprotocol/.node-version:
--------------------------------------------------------------------------------

```
22.14.0

```

--------------------------------------------------------------------------------
/evals/.gitignore:
--------------------------------------------------------------------------------

```
node_modules
.env

```

--------------------------------------------------------------------------------
/modelcontextprotocol/.gitignore:
--------------------------------------------------------------------------------

```
dist/
node_modules/
*.dxt
dxt-dist/
```

--------------------------------------------------------------------------------
/python/examples/openai/.env.template:
--------------------------------------------------------------------------------

```
OPENAI_API_KEY=""
STRIPE_SECRET_KEY=""
```

--------------------------------------------------------------------------------
/typescript/examples/openai/.env.template:
--------------------------------------------------------------------------------

```
OPENAI_API_KEY=""
STRIPE_SECRET_KEY=""
```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/.gitignore:
--------------------------------------------------------------------------------

```
# wrangler project
.dev.vars
.wrangler/

```

--------------------------------------------------------------------------------
/typescript/examples/langchain/.env.template:
--------------------------------------------------------------------------------

```
LANGSMITH_API_KEY=""
STRIPE_SECRET_KEY=""

```

--------------------------------------------------------------------------------
/modelcontextprotocol/.dxtignore:
--------------------------------------------------------------------------------

```
node_modules/
node_modules/**/*.*
tsconfig.json
Dockerfile
```

--------------------------------------------------------------------------------
/python/examples/openai/web_search/.env.template:
--------------------------------------------------------------------------------

```
OPENAI_API_KEY=""
STRIPE_SECRET_KEY=""
STRIPE_CUSTOMER_ID=""
STRIPE_METER=""

```

--------------------------------------------------------------------------------
/modelcontextprotocol/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "singleQuote": true,
  "trailingComma": "es5",
  "bracketSpacing": false
}

```

--------------------------------------------------------------------------------
/typescript/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "singleQuote": true,
  "trailingComma": "es5",
  "bracketSpacing": false
}

```

--------------------------------------------------------------------------------
/python/examples/crewai/.env.template:
--------------------------------------------------------------------------------

```
STRIPE_SECRET_KEY=""
OPENAI_API_BASE=""
OPENAI_MODEL_NAME="gpt-4o"
OPENAI_API_KEY=""

```

--------------------------------------------------------------------------------
/typescript/examples/ai-sdk/.env.template:
--------------------------------------------------------------------------------

```
STRIPE_SECRET_KEY=""
STRIPE_CUSTOMER_ID=""
STRIPE_METER_INPUT=""
STRIPE_METER_OUTPUT=""

```

--------------------------------------------------------------------------------
/python/examples/langchain/.env.template:
--------------------------------------------------------------------------------

```
LANGSMITH_API_KEY=""
STRIPE_SECRET_KEY=""
OPENAI_API_BASE=""
OPENAI_MODEL_NAME="gpt-4o"
OPENAI_API_KEY=""

```

--------------------------------------------------------------------------------
/evals/.env.example:
--------------------------------------------------------------------------------

```
BRAINTRUST_API_KEY=...
STRIPE_SECRET_KEY=...

# Enable if your OpenAI API is behind a unix socket proxy:
# SOCKET_PROXY_PATH=""
OPENAI_BASE_URL=http://0.0.0.0:8000/v1
OPENAI_API_KEY=EMPTY




```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/.dev.vars.example:
--------------------------------------------------------------------------------

```
STRIPE_SECRET_KEY=sk_test_......
STRIPE_PRICE_ID_ONE_TIME_PAYMENT=price_1RJJwjR1b4cWtS0UCIDTSU3V
STRIPE_PRICE_ID_SUBSCRIPTION=price_1RJJwjR1bGyW9S0UCIDTSU3V
STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION=price_1RJdGWR1bGyW9S0UucbYBFBZ
```

--------------------------------------------------------------------------------
/python/.editorconfig:
--------------------------------------------------------------------------------

```
; https://editorconfig.org/

root = true

[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8

[*.{cfg,ini,json,toml,yml}]
indent_size = 2

[Makefile]
indent_style = tab

```

--------------------------------------------------------------------------------
/python/examples/openai/customer_support/.env.template:
--------------------------------------------------------------------------------

```
OPENAI_API_KEY=your_openai_api_key
STRIPE_SECRET_KEY=your_stripe_secret_key

# Your email address and app password
# https://support.google.com/accounts/answer/185833?hl=en
[email protected]
EMAIL_PASSWORD=your_app_specific_password
# Only respond to emails send to this address (defaults to $EMAIL_ADDRESS)
SUPPORT_ADDRESS="[email protected]"

# If your connecting to gmail, you don't need to customize these
IMAP_SERVER=imap.gmail.com
IMAP_PORT=993
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587

```

--------------------------------------------------------------------------------
/python/.flake8:
--------------------------------------------------------------------------------

```
[flake8]
# E501 is the "Line too long" error. We disable it because we use Black for
# code formatting. Black makes a best effort to keep lines under the max
# length, but can go over in some cases.
# W503 goes against PEP8 rules. It's disabled by default, but must be disabled
# explicitly when using `ignore`.
# E704 is disabled in the default configuration, but by specifying `ignore`, we wipe that out.
# ruff formatting creates code that violates it, so we have to disable it manually
ignore = E501, W503, E704
per-file-ignores =
    # setup.py is required for tooling
    setup.py: IMP102

```

--------------------------------------------------------------------------------
/typescript/.gitignore:
--------------------------------------------------------------------------------

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/
junk/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

.turbo


/cloudflare/
/openai/
/ai-sdk/
/langchain/
/modelcontextprotocol/
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Node modules
node_modules/
junk/

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

```

--------------------------------------------------------------------------------
/python/examples/crewai/README.md:
--------------------------------------------------------------------------------

```markdown
# CrewAI Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
python main.py
```

```

--------------------------------------------------------------------------------
/python/examples/langchain/README.md:
--------------------------------------------------------------------------------

```markdown
# LangChain Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
python main.py
```

```

--------------------------------------------------------------------------------
/typescript/examples/ai-sdk/README.md:
--------------------------------------------------------------------------------

```markdown
# AI SDK Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
npx ts-node index.ts --env
```

```

--------------------------------------------------------------------------------
/typescript/examples/openai/README.md:
--------------------------------------------------------------------------------

```markdown
# OpenAI Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
npx ts-node index.ts --env
```

```

--------------------------------------------------------------------------------
/typescript/examples/langchain/README.md:
--------------------------------------------------------------------------------

```markdown
# LangChain Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
npx ts-node index.ts --env
```

```

--------------------------------------------------------------------------------
/python/examples/openai/file_search/README.md:
--------------------------------------------------------------------------------

```markdown
# Web Search Example

This example shows how to use the Stripe Agent Toolkit with OpenAI to create an agent that can search the web and charge for outcomes.

## Setup

1. Create a OpenAI Vector Store following the [OpenAI documentation](https://platform.openai.com/docs/api-reference/vector-stores-files) and add the files you want to search.

2. Copy `.env.template` to `.env` populate with the relevant values.

```bash
OPENAI_API_KEY=your_openai_api_key
OPENAI_VECTOR_STORE_ID=your_openai_vector_store_id
STRIPE_SECRET_KEY=your_stripe_secret_key
```

## Usage

```bash
python main.py
```

You can see the invoices created in the Stripe Dashboard.

```

--------------------------------------------------------------------------------
/python/examples/openai/web_search/README.md:
--------------------------------------------------------------------------------

```markdown
# Web Search Example

This example shows how to use the Stripe Agent Toolkit with OpenAI to create an agent that can search the web and charge for outcomes.

## Setup

1. Create a Stripe Billing Meter and Stripe Customer following the [Stripe documentation](https://docs.stripe.com/billing/subscriptions/usage-based/implementation-guide).

2. Copy `.env.template` to `.env` populate with the relevant values.

```bash
OPENAI_API_KEY=your_openai_api_key
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_CUSTOMER_ID=your_stripe_customer_id
STRIPE_METER=your_stripe_meter
```

## Usage

```bash
python main.py
```

You can see the usage in the Stripe Dashboard.

```

--------------------------------------------------------------------------------
/typescript/src/modelcontextprotocol/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP Payments

A simple MCP server helper to require payment to use tools, whether subscription or usage-based.

This implementation works on Vercel with a standard MCP server.

## Usage Instructions for `registerPaidTool`

1. Import the `registerPaidTool` function from this package.
2. Call `registerPaidTool` with your MCP server, tool name, description, params schema, callback, and payment options.
3. Example usage:

```ts
import {registerPaidTool} from './register-paid-tool';
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';

const server = new McpServer({
  name: 'mcp-typescript server',
  version: '0.1.0',
});

registerPaidTool(
  server,
  'add_numbers',
  {
    a: z.number(),
    b: z.number(),
  },
  ({a, b}) => {
    return {
      content: [{type: 'text', text: `Result: ${a + b}`}],
    };
  },
  {
    priceId: '{{PRICE_ID}}',
    successUrl: '{{CALLBACK_URL}}',
    email: '{{EMAIL}}',
    paymentReason:
      'You must pay a subscription to add two big numbers together.',
    stripeSecretKey: '{{SECRET_KEY}}',
  }
);
```

```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/README.md:
--------------------------------------------------------------------------------

```markdown
# PaidMcpAgent

An example of how to monetize an MCP server with Stripe.

## Setup

1. Copy `.dev.vars.example` to `.dev.vars` and add your Stripe API key.
2. Configure the required Stripe environment variables:
   - `STRIPE_SECRET_KEY`: Your Stripe secret key
   - `STRIPE_ONETIME_SUBSCRIPTION_PRICE_ID`: Price ID for one-time payment
   - `STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION`: Price ID for usage-based subscription
   - `STRIPE_METER_EVENT_NAME`: Event name for usage metering
3. This demo uses an example fake OAuth implementation for the MCP server. We recommend following the [authorization](https://developers.cloudflare.com/agents/model-context-protocol/authorization/) Cloudflare docs.

## Development

```
pnpm i
pnpm dev
```

## Testing

Open up the inspector and connect to your MCP server.

```
npx @modelcontextprotocol/inspector@latest http://localhost:4242/sse
```

### Deploy

```
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler secret put STRIPE_PRICE_ID_ONE_TIME_PAYMENT
npx wrangler secret put STRIPE_ONETIME_SUBSCRIPTION_PRICE_ID
npx wrangler secret put STRIPE_PRICE_ID_USAGE_BASED_SUBSCRIPTION
```

### Feedback

Please leave feedback throught the GitHub issues and discussions on how you
would use the `PaidMcpAgent`!

```

--------------------------------------------------------------------------------
/evals/README.md:
--------------------------------------------------------------------------------

```markdown
## Evals

Set up `.env` file with the following (see `.env.example` for an example):

```
BRAINTRUST_API_KEY=...
STRIPE_SECRET_KEY=sk_test_....
OPENAI_BASE_URL=http://0.0.0.0:8000/v1
OPENAI_API_KEY=EMPTY
```

To run:

```
tsx eval.ts
```

We are using [Braintrust](https://www.braintrust.dev/) to run the evals.

## Framework

There is a very lightweight built-in testing framework that wraps Braintrust to make adding new test cases easy.

Add a new test case to `cases.ts`:

```javascript
test({
  prompt:
    "Create a product called 'Test Product' with a description 'A test product for evaluation'",
  fn: ({ toolCalls, messages }) => [
    expectToolCall(toolCalls, ["create_product"]),
    llmCriteriaMet(
      messages,
      "The message should include a successful production creation response"
    ),
  ],
});
```

The Typescript type defintions has documentation to help you. The `fn` function
will be called with the resulting output of your prompt. This should return an array of "assertions." These are like `expect` in Jest.

This can be as simple as noting a tool was called or as complex as asking an LLM to do semantic similarities. See `scorer.ts` for a list of assertions.

Override the toolkit config by passing a `toolkitConfig` object.

If your test case needs some set up, for example, if it needs to set up some state in the Stripe account or load data, you can pass an async function.

```javascript
test(async () => {
  const customers = await stripe.customers.list();

  return {
    prompt: "What are my payments",
    toolkitConfig: {
      context: {
        customer: customers.data[0].id,
      },
    },
    fn: ({ toolCalls, messages }) => [],
  };
});
```

```

--------------------------------------------------------------------------------
/python/examples/openai/customer_support/README.md:
--------------------------------------------------------------------------------

```markdown
# Email Support Agent

Sample app to help you automate your email support. Powered by OpenAI's Agent SDK and [Stripe Agent Toolkit](https://github.com/stripe/agent-toolkit).

Customize this agent to fit your own needs by cloning and modifying [support_agent.py](./support_agent.py).

## Features

The support agent currently can:

- Answer FAQ questions
- Update billing information through Customer Portal
- Send any missing invoices

We also support a REPL to help you test your agent without sending a gazillion emails.

## How it Works

The support agent will:

- Connects to your email using an IMAP client
- Checks for unread emails every 30 seconds
- Generates and sends a reply
- Marks the emails as read

If it doesn't know how to answer the question, it will not respond and ignore the email.

## Setup

1. Install uv (if not already installed):

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

2. Clone this repository
3. Create and activate a virtual environment:

```bash
uv venv
source .venv/bin/activate  # On Unix/macOS
# or
.venv\Scripts\activate  # On Windows
```

4. Install dependencies:

```bash
uv sync
```

5. Copy `.env.example` to `.env`:

```bash
cp .env.example .env
```

6. Configure your `.env` file with:
   - Email credentials (create an [app-specific password](https://support.google.com/accounts/answer/185833) for Gmail)
   - IMAP/SMTP server details (defaults for Gmail provided in .env.example)
   - OpenAI API key
   - Stripe Secret Key

## Usage

Run the agent:

```bash
python main.py
```

Run the REPL with:

```bash
python repl.py
```

## Customize for your App

This repository is just a sample app tailored to our [example website](http://standupjack.com).

We recommend cloning this repository and customizing the system prompt and tools in [support_agent.py](./support_agent.py). It's very easy to add new capabilities.

```

--------------------------------------------------------------------------------
/typescript/src/cloudflare/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP Payments

`PaidMcpAgent` extends [Cloudflare's `McpAgent`](https://github.com/cloudflare/agents) to make it simple to require payment to use tools, whether subscription or usage-based. For a full end-to-end example, see [/examples/cloudflare](../../examples/cloudflare/).

## Usage

### Setup

```
npm install @stripe/agent-toolkit
```

Modify your existing MCP server by extending with `PaidMcpAgent` instead of `McpAgent`.

```ts
import {
  PaymentState,
  experimental_PaidMcpAgent as PaidMcpAgent,
} from '@stripe/agent-toolkit/cloudflare';

type Props = {
  userEmail: string;
};

type State = PaymentState & {};

export class MyMCP extends PaidMcpAgent<Bindings, State, Props> {}
```

Lastly, set your `STRIPE_SECRET_KEY` in `.dev.vars` to test, and then `npx wrangler secret put STRIPE_SECRET_KEY` when ready for production.

### Monetizing a tool

Consider a basic tool that can add two numbers together:

```ts
this.server.tool('add', {a: z.number(), b: z.number()}, ({a, b}) => {
  return {
    content: [{type: 'text', text: `Result: ${a + b}`}],
  };
});
```

To make this paid using a subscription, first create a product and price in the Stripe Dashboard.

Then, replace `this.server.tool` with `this.paidTool` and add your payment config: `priceId`, `paymentReason`, and `successUrl`.

```ts
this.paidTool(
  'add_numbers',
  {
    a: z.number(),
    b: z.number(),
  },
  ({a, b}) => {
    return {
      content: [{type: 'text', text: `Result: ${a + b}`}],
    };
  },
  {
    priceId: '{{PRICE_ID}}',
    successUrl: 'https://mcp.mysite.com/success',
    paymentReason:
      'You must pay a subscription to add two big numbers together.',
  }
);
```

## Authentication

`PaidMcp` relies on `props.userEmail` to identify (or create) a Stripe customer. You can prepopulate this directly, or integrate with `OAuthProvider` from `@cloudflare/workers-oauth-provider` to set the prop on succesful authentication.

```

--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------

```markdown
# Stripe Agent Toolkit - Python

The Stripe Agent Toolkit library enables popular agent frameworks including OpenAI's Agent SDK, LangChain, and CrewAI to integrate with Stripe APIs through function calling. The
library is not exhaustive of the entire Stripe API. It is built directly on top
of the [Stripe Python SDK][python-sdk].

## Installation

You don't need this source code unless you want to modify the package. If you just
want to use the package, just run:

```sh
pip install stripe-agent-toolkit
```

### Requirements

-   Python 3.11+

## Usage

The library needs to be configured with your account's secret key which is
available in your [Stripe Dashboard][api-keys].

```python
from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key="sk_test_...",
    configuration={
        "actions": {
            "payment_links": {
                "create": True,
            },
        }
    },
)
```

The toolkit works with OpenAI's Agent SDK, LangChain, and CrewAI and can be passed as a list of tools. For example:

```python
from agents import Agent

stripe_agent = Agent(
    name="Stripe Agent",
    instructions="You are an expert at integrating with Stripe",
    tools=stripe_agent_toolkit.get_tools()
)
```

Examples for OpenAI's Agent SDK,LangChain, and CrewAI are included in [/examples](/examples).

[python-sdk]: https://github.com/stripe/stripe-python
[api-keys]: https://dashboard.stripe.com/account/apikeys

#### Context

In some cases you will want to provide values that serve as defaults when making requests. Currently, the `account` context value enables you to make API calls for your [connected accounts](https://docs.stripe.com/connect/authentication).

```python
stripe_agent_toolkit = StripeAgentToolkit(
    secret_key="sk_test_...",
    configuration={
        "context": {
            "account": "acct_123"
        }
    }
)
```

## Development

```
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

```

--------------------------------------------------------------------------------
/typescript/README.md:
--------------------------------------------------------------------------------

```markdown
# Stripe Agent Toolkit - TypeScript

The Stripe Agent Toolkit enables popular agent frameworks including LangChain and Vercel's AI SDK to integrate with Stripe APIs through function calling. It also provides tooling to quickly integrate metered billing for prompt and completion token usage.

## Installation

You don't need this source code unless you want to modify the package. If you just
want to use the package run:

```
npm install @stripe/agent-toolkit
```

### Requirements

- Node 18+

## Usage

The library needs to be configured with your account's secret key which is available in your [Stripe Dashboard][api-keys]. Additionally, `configuration` enables you to specify the types of actions that can be taken using the toolkit.

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/langchain';

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
    },
  },
});
```

### Tools

The toolkit works with LangChain and Vercel's AI SDK and can be passed as a list of tools. For example:

```typescript
import {AgentExecutor, createStructuredChatAgent} from 'langchain/agents';

const tools = stripeAgentToolkit.getTools();

const agent = await createStructuredChatAgent({
  llm,
  tools,
  prompt,
});

const agentExecutor = new AgentExecutor({
  agent,
  tools,
});
```

#### Context

In some cases you will want to provide values that serve as defaults when making requests. Currently, the `account` context value enables you to make API calls for your [connected accounts](https://docs.stripe.com/connect/authentication).

```typescript
const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    context: {
      account: 'acct_123',
    },
  },
});
```

### Metered billing

For Vercel's AI SDK, you can use middleware to submit billing events for usage. All that is required is the customer ID and the input/output meters to bill.

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/ai-sdk';
import {openai} from '@ai-sdk/openai';
import {
  generateText,
  experimental_wrapLanguageModel as wrapLanguageModel,
} from 'ai';

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
    },
  },
});

const model = wrapLanguageModel({
  model: openai('gpt-4o'),
  middleware: stripeAgentToolkit.middleware({
    billing: {
      customer: 'cus_123',
      meters: {
        input: 'input_tokens',
        output: 'output_tokens',
      },
    },
  }),
});
```

This works with both `generateText` and `generateStream` from the Vercel AI SDK.

## Model Context Protocol

The Stripe Agent Toolkit also supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.com/). See `/examples/modelcontextprotocol` for an example. The same configuration options are available, and the server can be run with all supported transports.

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/modelcontextprotocol';
import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
      products: {
        create: true,
      },
      prices: {
        create: true,
      },
    },
  },
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('Stripe MCP Server running on stdio');
}

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

[node-sdk]: https://github.com/stripe/stripe-node
[api-keys]: https://dashboard.stripe.com/account/apikeys

```

--------------------------------------------------------------------------------
/modelcontextprotocol/README.md:
--------------------------------------------------------------------------------

```markdown
# Stripe Model Context Protocol

The Stripe [Model Context Protocol](https://modelcontextprotocol.com/) server allows you to integrate with Stripe APIs through function calling. This protocol supports various tools to interact with different Stripe services.

## Setup

Stripe hosts a remote MCP server at https://mcp.stripe.com. View the docs [here](https://docs.stripe.com/mcp). To run the Stripe MCP server locally using npx, use the following command:

```bash
# To set up all available tools
npx -y @stripe/mcp --tools=all --api-key=YOUR_STRIPE_SECRET_KEY

# To set up specific tools
npx -y @stripe/mcp --tools=customers.create,customers.read,products.create --api-key=YOUR_STRIPE_SECRET_KEY

# To configure a Stripe connected account
npx -y @stripe/mcp --tools=all --api-key=YOUR_STRIPE_SECRET_KEY --stripe-account=CONNECTED_ACCOUNT_ID
```

Make sure to replace `YOUR_STRIPE_SECRET_KEY` with your actual Stripe secret key. Alternatively, you could set the STRIPE_SECRET_KEY in your environment variables.

### Usage with Claude Desktop

Add the following to your `claude_desktop_config.json`. See [here](https://modelcontextprotocol.io/quickstart/user) for more details.

```
{
  "mcpServers": {
    "stripe": {
      "command": "npx",
      "args": [
          "-y",
          "@stripe/mcp",
          "--tools=all",
          "--api-key=STRIPE_SECRET_KEY"
      ]
    }
  }
}
```

of if you're using Docker

```
{
    “mcpServers”: {
        “stripe”: {
            “command”: “docker",
            “args”: [
                “run”,
                "--rm",
                "-i",
                “mcp/stripe”,
                “--tools=all”,
                “--api-key=STRIPE_SECRET_KEY”
            ]
        }
    }
}

```

### Usage with Gemini CLI

1. Install [Gemini CLI](https://google-gemini.github.io/gemini-cli/#-installation) through your preferred method.
2. Install the Stripe MCP extension: `gemini extensions install https://github.com/stripe/agent-toolkit`.
3. Start Gemini CLI: `gemini`.
4. Go through the OAUTH flow: `/mcp auth stripe`.

## Available tools

| Tool                   | Description                     |
| ---------------------- | ------------------------------- |
| `balance.read`         | Retrieve balance information    |
| `coupons.create`       | Create a new coupon             |
| `coupons.read`         | Read coupon information         |
| `customers.create`     | Create a new customer           |
| `customers.read`       | Read customer information       |
| `disputes.read`        | Read disputes information       |
| `disputes.update`      | Update an existing dispute      |
| `documentation.read`   | Search Stripe documentation     |
| `invoiceItems.create`  | Create a new invoice item       |
| `invoices.create`      | Create a new invoice            |
| `invoices.read`        | Read invoice information        |
| `invoices.update`      | Update an existing invoice      |
| `paymentIntents.read`  | Read payment intent information |
| `paymentLinks.create`  | Create a new payment link       |
| `prices.create`        | Create a new price              |
| `prices.read`          | Read price information          |
| `products.create`      | Create a new product            |
| `products.read`        | Read product information        |
| `refunds.create`       | Create a new refund             |
| `subscriptions.read`   | Read subscription information   |
| `subscriptions.update` | Update subscription information |

## Debugging the Server

To debug your server, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector).

First build the server

```
npm run build
```

Run the following command in your terminal:

```bash
# Start MCP Inspector and server with all tools
npx @modelcontextprotocol/inspector node dist/index.js --tools=all --api-key=YOUR_STRIPE_SECRET_KEY
```

### Build using Docker

First build the server

```
docker build -t mcp/stripe .
```

Run the following command in your terminal:

```bash
docker run -p 3000:3000 -p 5173:5173 -v /var/run/docker.sock:/var/run/docker.sock mcp/inspector docker run --rm -i mcp/stripe --tools=all --api-key=YOUR_STRIPE_SECRET_KEY

```

### Instructions

1. Replace `YOUR_STRIPE_SECRET_KEY` with your actual Stripe API secret key.
2. Run the command to start the MCP Inspector.
3. Open the MCP Inspector UI in your browser and click Connect to start the MCP server.
4. You can see the list of tools you selected and test each tool individually.

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Stripe Agent Toolkit

The Stripe Agent Toolkit enables popular agent frameworks including Model Context Protocol (MCP), OpenAI's Agent SDK, LangChain, CrewAI, and Vercel's AI SDK to integrate with Stripe APIs through function calling. The
library is not exhaustive of the entire Stripe API. It includes support for MCP, Python, and TypeScript and is built directly on top of the Stripe [Python][python-sdk] and [Node][node-sdk] SDKs.

Included below are basic instructions, but refer to the [MCP](/modelcontextprotocol) [Python](/python), [TypeScript](/typescript) packages for more information.

## Model Context Protocol

Stripe hosts a remote MCP server at `https://mcp.stripe.com`. This allows secure MCP client access via OAuth. View the docs [here](https://docs.stripe.com/mcp#remote).

The Stripe Agent Toolkit also exposes tools in the [Model Context Protocol (MCP)](https://modelcontextprotocol.com/) format.  Or, to run a local Stripe MCP server using npx, use the following command:

```bash
npx -y @stripe/mcp --tools=all --api-key=YOUR_STRIPE_SECRET_KEY
```

## Python

### Installation

You don't need this source code unless you want to modify the package. If you just
want to use the package run:

```sh
pip install stripe-agent-toolkit
```

#### Requirements

- Python 3.11+

### Usage

The library needs to be configured with your account's secret key which is
available in your [Stripe Dashboard][api-keys].

```python
from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key="sk_test_...",
    configuration={
        "actions": {
            "payment_links": {
                "create": True,
            },
        }
    },
)
```

The toolkit works with OpenAI's Agent SDK, LangChain, and CrewAI and can be passed as a list of tools. For example:

```python
from agents import Agent

stripe_agent = Agent(
    name="Stripe Agent",
    instructions="You are an expert at integrating with Stripe",
    tools=stripe_agent_toolkit.get_tools()
)
```

Examples for OpenAI's Agent SDK,LangChain, and CrewAI are included in [/examples](/python/examples).

#### Context

In some cases you will want to provide values that serve as defaults when making requests. Currently, the `account` context value enables you to make API calls for your [connected accounts](https://docs.stripe.com/connect/authentication).

```python
stripe_agent_toolkit = StripeAgentToolkit(
    secret_key="sk_test_...",
    configuration={
        "context": {
            "account": "acct_123"
        }
    }
)
```

## TypeScript

### Installation

You don't need this source code unless you want to modify the package. If you just
want to use the package run:

```
npm install @stripe/agent-toolkit
```

#### Requirements

- Node 18+

### Usage

The library needs to be configured with your account's secret key which is available in your [Stripe Dashboard][api-keys]. Additionally, `configuration` enables you to specify the types of actions that can be taken using the toolkit.

```typescript
import { StripeAgentToolkit } from "@stripe/agent-toolkit/langchain";

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
    },
  },
});
```

#### Tools

The toolkit works with LangChain and Vercel's AI SDK and can be passed as a list of tools. For example:

```typescript
import { AgentExecutor, createStructuredChatAgent } from "langchain/agents";

const tools = stripeAgentToolkit.getTools();

const agent = await createStructuredChatAgent({
  llm,
  tools,
  prompt,
});

const agentExecutor = new AgentExecutor({
  agent,
  tools,
});
```

#### Context

In some cases you will want to provide values that serve as defaults when making requests. Currently, the `account` context value enables you to make API calls for your [connected accounts](https://docs.stripe.com/connect/authentication).

```typescript
const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    context: {
      account: "acct_123",
    },
  },
});
```

#### Metered billing

For Vercel's AI SDK, you can use middleware to submit billing events for usage. All that is required is the customer ID and the input/output meters to bill.

```typescript
import { StripeAgentToolkit } from "@stripe/agent-toolkit/ai-sdk";
import { openai } from "@ai-sdk/openai";
import {
  generateText,
  experimental_wrapLanguageModel as wrapLanguageModel,
} from "ai";

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
    },
  },
});

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: stripeAgentToolkit.middleware({
    billing: {
      customer: "cus_123",
      meters: {
        input: "input_tokens",
        output: "output_tokens",
      },
    },
  }),
});
```



## Supported API methods

- [Cancel a subscription](https://docs.stripe.com/api/subscriptions/cancel)
- [Create a coupon](https://docs.stripe.com/api/coupons/create)
- [Create a customer](https://docs.stripe.com/api/customers/create)
- [Create a payment link](https://docs.stripe.com/api/payment-link/create)
- [Create a price](https://docs.stripe.com/api/prices/create)
- [Create a product](https://docs.stripe.com/api/products/create)
- [Create a refund](https://docs.stripe.com/api/refunds/create)
- [Create an invoice item](https://docs.stripe.com/api/invoiceitems/create)
- [Create an invoice](https://docs.stripe.com/api/invoices/create)
- [Finalize an invoice](https://docs.stripe.com/api/invoices/finalize)
- [List all coupons](https://docs.stripe.com/api/coupons/list)
- [List all customers](https://docs.stripe.com/api/customers/list)
- [List all disputes](https://docs.stripe.com/api/disputes/list)
- [List all prices](https://docs.stripe.com/api/prices/list)
- [List all products](https://docs.stripe.com/api/products/list)
- [List all subscriptions](https://docs.stripe.com/api/subscriptions/list)
- [Retrieve balance](https://docs.stripe.com/api/balance/balance_retrieve)
- [Update a dispute](https://docs.stripe.com/api/disputes/update)
- [Update a subscription](https://docs.stripe.com/api/subscriptions/update)

[python-sdk]: https://github.com/stripe/stripe-python
[node-sdk]: https://github.com/stripe/stripe-node
[api-keys]: https://dashboard.stripe.com/account/apikeys

```

--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
# Security Policy

### Reporting a vulnerability

Please do not open GitHub issues or pull requests - this makes the problem immediately visible to everyone, including malicious actors.   

Security issues in this open-source project can be safely reported to Stripe's [Vulnerability Disclosure and Reward Program](https://stripe.com/docs/security/stripe#disclosure-and-reward-program).
Stripe's security team will triage your report and respond according to its impact on Stripe users and systems.

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing

Contributions of any kind are welcome! If you've found a bug or have a feature request, please feel free to [open an issue](/issues). 

<!-- We will try and respond to your issue or pull request within a week. -->

To make changes yourself, follow these steps:

1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository and [clone](https://help.github.com/articles/cloning-a-repository/) it locally.
<!-- 1. TODO add install step(s), e.g. "Run `npm install`" -->
<!-- 1. TODO add build step(s), e.g. "Build the library using `npm run build`" -->
2. Make your changes
<!-- 1. TODO add test step(s), e.g. "Test your changes with `npm test`" -->
3. Submit a [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)

## Contributor License Agreement ([CLA](https://en.wikipedia.org/wiki/Contributor_License_Agreement))

Once you have submitted a pull request, sign the CLA by clicking on the badge in the comment from [@CLAassistant](https://github.com/CLAassistant).

<img width="910" alt="image" src="https://user-images.githubusercontent.com/62121649/198740836-70aeb322-5755-49fc-af55-93c8e8a39058.png">

<br />
Thanks for contributing to Stripe! :sparkles:

```

--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------

```markdown
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [email protected]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

```

--------------------------------------------------------------------------------
/python/examples/langchain/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/python/tests/__init__.py:
--------------------------------------------------------------------------------

```python

```

--------------------------------------------------------------------------------
/typescript/pnpm-workspace.yaml:
--------------------------------------------------------------------------------

```yaml
packages:
  - '.'
  - 'examples/*'

```

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

```typescript
import StripeAgentToolkit from './toolkit';
export {StripeAgentToolkit};

```

--------------------------------------------------------------------------------
/typescript/src/langchain/index.ts:
--------------------------------------------------------------------------------

```typescript
import StripeAgentToolkit from './toolkit';
export {StripeAgentToolkit};

```

--------------------------------------------------------------------------------
/typescript/src/openai/index.ts:
--------------------------------------------------------------------------------

```typescript
import StripeAgentToolkit from './toolkit';
export {StripeAgentToolkit};

```

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

```python
"""Stripe Agent Toolkit for Strands."""

from .toolkit import StripeAgentToolkit

__all__ = ["StripeAgentToolkit"]

```

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

```typescript
import StripeAgentToolkit from './toolkit';
import {registerPaidTool} from './register-paid-tool';
export {StripeAgentToolkit, registerPaidTool};

```

--------------------------------------------------------------------------------
/typescript/examples/ai-sdk/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["index.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/typescript/examples/langchain/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["index.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/typescript/examples/openai/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["index.ts"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------

```json
{
  "recommendations": [
    "EditorConfig.editorconfig", // default
    "ms-python.python", // intellisense
    "ms-python.flake8", // linting
    "charliermarsh.ruff" // formatting
  ]
}

```

--------------------------------------------------------------------------------
/gemini-extension.json:
--------------------------------------------------------------------------------

```json
{
  "name": "stripe-gemini-mcp-extension",
  "version": "0.1.0",
  "mcpServers": {
    "stripe": {
      "httpUrl": "https://mcp.stripe.com",
      "oauth": {
        "enabled": true
      }
    }
  }
}

```

--------------------------------------------------------------------------------
/python/requirements.txt:
--------------------------------------------------------------------------------

```
twine
crewai==0.76.2
crewai-tools===0.13.2
flake8
langchain==0.3.4
langchain-openai==0.2.2
mypy==1.7.0
pydantic>=2.10
pyright==1.1.350
python-dotenv==1.0.1
ruff==0.4.4
stripe==11.0.0
openai==1.66.0
openai-agents==0.0.2

```

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

```typescript
// @ts-ignore
import emojiFromText from 'emoji-from-text';

export function generateImage(description: string) {
  const emoji = emojiFromText(description);
  try {
    return emoji[0].match.emoji.char;
  } catch (e) {
    return '⚠️ (Error generating image)';
  }
}

```

--------------------------------------------------------------------------------
/modelcontextprotocol/jest.config.ts:
--------------------------------------------------------------------------------

```typescript
import type {Config} from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/test/**/*.ts?(x)'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};

export default config;

```

--------------------------------------------------------------------------------
/typescript/jest.config.ts:
--------------------------------------------------------------------------------

```typescript
import type {Config} from 'jest';

const config: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/test/**/*.ts?(x)'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
};

export default config;

```

--------------------------------------------------------------------------------
/modelcontextprotocol/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------

```yaml
blank_issues_enabled: false
contact_links:
  - name: Stripe support
    url: https://support.stripe.com/
    about: |
      Please only file issues here that you believe represent actual bugs or feature requests for the Stripe Agent Tools library.

      If you're having general trouble with your Stripe integration, please reach out to support.

```

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

```python
from os import getenv

from dotenv import load_dotenv

# Load the environment
load_dotenv()


def ensure(name: str) -> str:
    var = getenv(name)
    if not var:
        raise ValueError(f"Missing '{name}' environment variable")
    return var


def get_or(name: str, default: str) -> str:
    var = getenv(name)
    if not var:
        return default
    return var

```

--------------------------------------------------------------------------------
/evals/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "es2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["../typescript/src/*"]
    }
  },
  "include": ["**/*.ts"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/typescript/examples/openai/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "stripe-agent-toolkit-examples-openai",
  "version": "0.1.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "dotenv": "^16.4.5",
    "openai": "^4.86.1",
    "@stripe/agent-toolkit": "latest"
  },
  "devDependencies": {
    "@types/node": "^22.7.4"
  }
}

```

--------------------------------------------------------------------------------
/modelcontextprotocol/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# syntax=docker/dockerfile:1
# check=experimental=all

FROM node:22-alpine@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad07487d24d1aa386336d1944
RUN npm install -g typescript pnpm
RUN addgroup -S group && adduser -S user -G group
WORKDIR /app
COPY . .
RUN --mount=type=cache,target=/root/.local \
    pnpm install --frozen-lockfile && pnpm run build

USER user
ENTRYPOINT ["node", "/app/dist/index.js"]

```

--------------------------------------------------------------------------------
/typescript/examples/ai-sdk/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "stripe-agent-toolkit-examples-ai-sdk",
  "version": "0.1.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@ai-sdk/openai": "^0.0.63",
    "@stripe/agent-toolkit": "latest",
    "ai": "^3.4.7 || ^4.0.0",
    "dotenv": "^16.4.5"
  },
  "devDependencies": {
    "@types/node": "^22.7.4"
  }
}

```

--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------

```json
{
  "editor.formatOnSave": true,
  "python.defaultInterpreterPath": "./venv/bin/python",
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
      "source.organizeImports": "never"
    }
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "ruff.lint.enable": false
}

```

--------------------------------------------------------------------------------
/python/examples/openai/customer_support/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "email-agent"
version = "0.1.0"
description = "An automated email support agent that uses AI to respond to customer support emails"
requires-python = ">=3.9"
dependencies = [
    "python-dotenv>=1.0.0",
    "imaplib2==3.6",
    "python-decouple==3.8",
    "openai-agents==0.0.2",
    "stripe-agent-toolkit>=0.6.0",
    "stripe>=7.0.0",
    "urllib3<2.0.0",
    "markdown==3.7"
]

[tool.hatch.metadata]
allow-direct-references = true

```

--------------------------------------------------------------------------------
/typescript/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Default",
  "compilerOptions": {
    "outDir": "./dist",
    "target": "es2022",
    "moduleDetection": "force",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true,
    "module": "NodeNext",
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "src/*"
      ]
    }
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "examples"
  ]
}
```

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

```typescript
import type {CoreTool} from 'ai';
import {tool} from 'ai';
import {z} from 'zod';
import StripeAPI from '../shared/api';

export default function StripeTool(
  stripeAPI: StripeAPI,
  method: string,
  description: string,
  schema: z.ZodObject<any, any, any, any, {[x: string]: any}>
): CoreTool {
  return tool({
    description: description,
    parameters: schema,
    execute: (arg: z.output<typeof schema>) => {
      return stripeAPI.run(method, arg);
    },
  });
}

```

--------------------------------------------------------------------------------
/typescript/examples/langchain/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "stripe-agent-toolkit-examples-langchain",
  "version": "0.1.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@langchain/core": "^0.3.6",
    "@langchain/openai": "^0.3.5",
    "@stripe/agent-toolkit": "latest",
    "dotenv": "^16.4.5",
    "langchain": "^0.3.2"
  },
  "devDependencies": {
    "@types/node": "^22.7.4"
  }
}

```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/biome.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
  "vcs": {
    "enabled": false,
    "clientKind": "git",
    "useIgnoreFile": false
  },
  "files": {
    "ignoreUnknown": false,
    "ignore": ["worker-configuration.d.ts"]
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "tab"
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double"
    }
  }
}

```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "es2021",
    "lib": [
      "es2021"
    ],
    "jsx": "react-jsx",
    "module": "es2022",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "noEmit": true,
    "isolatedModules": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "allowImportingTsExtensions": true,
    "strict": true,
    "skipLibCheck": true,
    "types": []
  },
  "include": [
    "worker-configuration.d.ts",
    "src/**/*.ts"
  ]
}
```

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

```typescript
import {createInvoiceItemPrompt} from '@/shared/invoiceItems/createInvoiceItem';

describe('createInvoiceItemPrompt', () => {
  it('should return the correct prompt when no customer is specified', () => {
    const prompt = createInvoiceItemPrompt({});
    expect(prompt).toContain('- customer (str)');
  });

  it('should return the correct prompt when a customer is specified', () => {
    const prompt = createInvoiceItemPrompt({customer: 'cus_123'});
    expect(prompt).toContain('context: cus_123');
    expect(prompt).not.toContain('- customer (str)');
  });
});

```

--------------------------------------------------------------------------------
/evals/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "evals",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "tsx eval.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@ai-sdk/openai": "^0.0.63",
    "@stripe/agent-toolkit": "file:../typescript",
    "ai": "^4.0.28",
    "autoevals": "^0.0.124",
    "braintrust": "^0.0.185",
    "dotenv": "^16.4.7",
    "lodash": "^4.17.21",
    "openai": "^4.91.1",
    "stripe": "^17.7.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/lodash": "^4.17.20",
    "tsx": "^4.19.3"
  }
}

```

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

```python
import asyncio

from agents import ItemHelpers, TResponseInputItem

import support_agent


async def main():
    """Simple REPL for testing your support agent"""
    input_items: list[TResponseInputItem] = []
    while True:
        user_input = input("Enter your message: ")
        input_items.append({"content": user_input, "role": "user"})
        result = await support_agent.run(input_items)
        output = ItemHelpers.text_message_outputs(result.new_items)
        print(f"Assistant: {output}")
        input_items = result.to_input_list()


if __name__ == "__main__":
    asyncio.run(main())

```

--------------------------------------------------------------------------------
/evals/braintrust_openai.ts:
--------------------------------------------------------------------------------

```typescript
require("dotenv").config();

import { wrapOpenAI } from "braintrust";
import OpenAI from "openai";
import * as http from "http";
import * as net from "net";

const httpAgent = process.env.SOCKET_PROXY_PATH
  ? new (class extends http.Agent {
      createConnection = (_: any, callback: Function) =>
        net.createConnection(process.env.SOCKET_PROXY_PATH!, () => callback());
    })()
  : undefined;

// This wrap function adds useful tracing in Braintrust
const openai: any = wrapOpenAI(
  new OpenAI({
    baseURL: process.env.OPENAI_BASE_URL,
    apiKey: process.env.OPENAI_API_KEY,
    httpAgent,
  })
);

export default openai;

```

--------------------------------------------------------------------------------
/typescript/src/test/shared/balance/parameters.test.ts:
--------------------------------------------------------------------------------

```typescript
import {retrieveBalanceParameters} from '@/shared/balance/retrieveBalance';

describe('retrieveBalanceParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = retrieveBalanceParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual([]);
    expect(fields.length).toBe(0);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = retrieveBalanceParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual([]);
    expect(fields.length).toBe(0);
  });
});

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/crewai/tool.py:
--------------------------------------------------------------------------------

```python
"""
This tool allows agents to interact with the Stripe API.
"""

from __future__ import annotations

from typing import Any, Optional, Type
from pydantic import BaseModel

from crewai_tools import BaseTool

from ..api import StripeAPI


class StripeTool(BaseTool):
    """Tool for interacting with the Stripe API."""

    stripe_api: StripeAPI
    method: str
    name: str = ""
    description: str = ""
    args_schema: Optional[Type[BaseModel]] = None

    def _run(
        self,
        *args: Any,
        **kwargs: Any,
    ) -> str:
        """Use the Stripe API to run an operation."""
        return self.stripe_api.run(self.method, *args, **kwargs)

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/langchain/tool.py:
--------------------------------------------------------------------------------

```python
"""
This tool allows agents to interact with the Stripe API.
"""

from __future__ import annotations

from typing import Any, Optional, Type
from pydantic import BaseModel

from langchain.tools import BaseTool

from ..api import StripeAPI


class StripeTool(BaseTool):
    """Tool for interacting with the Stripe API."""

    stripe_api: StripeAPI
    method: str
    name: str = ""
    description: str = ""
    args_schema: Optional[Type[BaseModel]] = None

    def _run(
        self,
        *args: Any,
        **kwargs: Any,
    ) -> str:
        """Use the Stripe API to run an operation."""
        return self.stripe_api.run(self.method, *args, **kwargs)

```

--------------------------------------------------------------------------------
/typescript/src/test/shared/refunds/parameters.test.ts:
--------------------------------------------------------------------------------

```typescript
import {createRefundParameters} from '@/shared/refunds/createRefund';

describe('createRefundParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createRefundParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['payment_intent', 'amount']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createRefundParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['payment_intent', 'amount']);
    expect(fields.length).toBe(2);
  });
});

```

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

```typescript
import {listPaymentIntentsParameters} from '@/shared/paymentIntents/listPaymentIntents';

describe('listPaymentIntentsParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listPaymentIntentsParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['customer', 'limit']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listPaymentIntentsParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit']);
    expect(fields.length).toBe(1);
  });
});

```

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

```typescript
import {createPaymentLinkParameters} from '@/shared/paymentLinks/createPaymentLink';

describe('createPaymentLinkParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createPaymentLinkParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['price', 'quantity', 'redirect_url']);
    expect(fields.length).toBe(3);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createPaymentLinkParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['price', 'quantity', 'redirect_url']);
  });
});

```

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

```typescript
import {createInvoiceItemParameters} from '@/shared/invoiceItems/createInvoiceItem';

describe('createInvoiceItemParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createInvoiceItemParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['customer', 'price', 'invoice']);
    expect(fields.length).toBe(3);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createInvoiceItemParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['price', 'invoice']);
    expect(fields.length).toBe(2);
  });
});

```

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

```typescript
import {searchDocumentationParameters} from '@/shared/documentation/searchDocumentation';

describe('searchDocumentationParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = searchDocumentationParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['question', 'language']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = searchDocumentationParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['question', 'language']);
    expect(fields.length).toBe(2);
  });
});

```

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

```typescript
import {listPaymentIntentsPrompt} from '@/shared/paymentIntents/listPaymentIntents';

describe('listPaymentIntentsPrompt', () => {
  it('should return the correct prompt', () => {
    const prompt = listPaymentIntentsPrompt();
    expect(prompt).toContain('customer');
  });

  it('should return the correct prompt when no customer is specified', () => {
    const prompt = listPaymentIntentsPrompt({});
    expect(prompt).toContain('- customer (str, optional)');
  });

  it('should return the correct prompt when a customer is specified', () => {
    const prompt = listPaymentIntentsPrompt({customer: 'cus_123'});
    expect(prompt).toContain('context: cus_123');
    expect(prompt).not.toContain('- customer (str, optional)');
  });
});

```

--------------------------------------------------------------------------------
/.github/workflows/npm_mcp_release.yml:
--------------------------------------------------------------------------------

```yaml
name: NPM Release @stripe/mcp

on:
  workflow_dispatch: {}

jobs:
  mcp-release:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./modelcontextprotocol
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4

      - name: pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.11.0

      # Setup .npmrc file to publish to npm
      - uses: actions/setup-node@v4
        with:
          node-version: "20.x"
          registry-url: "https://registry.npmjs.org"
      - run: pnpm install
      - run: pnpm run test
      - run: pnpm run build
      - run: npm publish --ignore-scripts --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

```

--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------

```json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Jest Current File",
            "runtimeExecutable": "sh",
            "program": "${workspaceFolder}/typescript/node_modules/.bin/jest",
            "args": [
                "${relativeFile}",
                "--config",
                "${workspaceFolder}/typescript/jest.config.ts"
            ],
            "console": "integratedTerminal",
            "internalConsoleOptions": "openOnFirstSessionStart"
        }
    ]
}
```

--------------------------------------------------------------------------------
/.github/workflows/npm_agent_toolkit_release.yml:
--------------------------------------------------------------------------------

```yaml
name: NPM Release @stripe/agent-toolkit

on:
  workflow_dispatch: {}

jobs:
  agent-toolkit-release:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./typescript
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      # Setup .npmrc file to publish to npm

      - name: pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.11.0

      - uses: actions/setup-node@v4
        with:
          node-version: "20.x"
          registry-url: "https://registry.npmjs.org"
      - run: pnpm install
      - run: pnpm run test
      - run: pnpm run build
      - run: npm publish --ignore-scripts --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

```

--------------------------------------------------------------------------------
/typescript/tsup.config.ts:
--------------------------------------------------------------------------------

```typescript
import {defineConfig} from 'tsup';

export default defineConfig([
  {
    entry: ['src/langchain/index.ts'],
    outDir: 'langchain',
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
  },
  {
    entry: ['src/ai-sdk/index.ts'],
    outDir: 'ai-sdk',
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
  },
  {
    entry: ['src/modelcontextprotocol/index.ts'],
    outDir: 'modelcontextprotocol',
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
  },
  {
    entry: ['src/openai/index.ts'],
    outDir: 'openai',
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
  },
  {
    entry: ['src/cloudflare/index.ts'],
    outDir: 'cloudflare',
    format: ['cjs', 'esm'],
    dts: true,
    sourcemap: true,
    external: ['cloudflare:workers'],
  },
]);

```

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

```python
import asyncio
import os

from dotenv import load_dotenv
load_dotenv()

from agents import Agent, Runner, WebSearchTool
from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=os.getenv("STRIPE_SECRET_KEY"),
    configuration={},
)

research_agent = Agent(
    name="Research Agent",
    instructions="You are an expert at research.",
    tools=[WebSearchTool()],
    hooks=stripe_agent_toolkit.billing_hook(
        type="outcome",
        customer=os.getenv("STRIPE_CUSTOMER_ID"),
        meter=os.getenv("STRIPE_METER"),
    ),
)

async def main():
    result = await Runner.run(
        research_agent,
        "search the web for 'global gdp' and give me the latest data.",
    )
    print(result.final_output)

if __name__ == "__main__":
    asyncio.run(main())

```

--------------------------------------------------------------------------------
/typescript/examples/cloudflare/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "remote-mcp-server",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "deploy": "wrangler deploy",
    "dev": "wrangler dev",
    "format": "biome format --write",
    "lint:fix": "biome lint --fix",
    "start": "wrangler dev",
    "cf-typegen": "wrangler types"
  },
  "devDependencies": {
    "@biomejs/biome": "1.9.4",
    "@cloudflare/workers-types": "^4.20250515.0",
    "typescript": "^5.8.3",
    "workers-mcp": "0.1.0-3",
    "wrangler": "^4.15.2"
  },
  "dependencies": {
    "@cloudflare/workers-oauth-provider": "^0.0.5",
    "@modelcontextprotocol/sdk": "1.11.3",
    "@stripe/agent-toolkit": "latest",
    "@types/node": "^22.15.18",
    "add": "^2.0.6",
    "agents": "^0.0.84",
    "dotenv": "^16.5.0",
    "emoji-from-text": "^1.1.13",
    "hono": "^4.7.9",
    "stripe": "^18.1.0",
    "zod": "^3.24.4"
  },
  "pnpm": {}
}

```

--------------------------------------------------------------------------------
/python/examples/strands/main.py:
--------------------------------------------------------------------------------

```python
import os
from dotenv import load_dotenv

from strands import Agent
from stripe_agent_toolkit.strands.toolkit import StripeAgentToolkit

load_dotenv()

# Initialize the Stripe Agent Toolkit
stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=os.getenv("STRIPE_SECRET_KEY"),
    configuration={
        "actions": {
            "payment_links": {
                "create": True,
            },
            "products": {
                "create": True,
            },
            "prices": {
                "create": True,
            },
        }
    },
)

# Get the Stripe tools
tools = stripe_agent_toolkit.get_tools()

# Create agent with Stripe tools
agent = Agent(
    tools=tools
)

# Test the agent
response = agent("""
    Create a payment link for a new product called 'test' with a price
    of $100. Come up with a funny description about buy bots,
    maybe a haiku.
""")

print(response)

```

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

```python
from typing import Any
from agents import AgentHooks, RunContextWrapper, Agent, Tool
from ..api import StripeAPI

class BillingHooks(AgentHooks):
    def __init__(self, stripe: StripeAPI, type: str, customer: str, meter: str = None, meters: dict[str, str] = None):
        self.type = type
        self.stripe = stripe
        self.customer = customer
        self.meter = meter
        self.meters = meters

    async def on_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None:
        if self.type == "outcome":
            self.stripe.create_meter_event(self.meter, self.customer)

        if self.type == "token":
            if self.meters["input"]:
                self.stripe.create_meter_event(self.meters["input"], self.customer, context.usage.input_tokens)
            if self.meters["output"]:
                self.stripe.create_meter_event(self.meters["output"], self.customer, context.usage.output_tokens)

```

--------------------------------------------------------------------------------
/typescript/src/langchain/tool.ts:
--------------------------------------------------------------------------------

```typescript
import {z} from 'zod';
import {StructuredTool} from '@langchain/core/tools';
import {CallbackManagerForToolRun} from '@langchain/core/callbacks/manager';
import {RunnableConfig} from '@langchain/core/runnables';
import StripeAPI from '../shared/api';

class StripeTool extends StructuredTool {
  stripeAPI: StripeAPI;

  method: string;

  name: string;

  description: string;

  schema: z.ZodObject<any, any, any, any>;

  constructor(
    StripeAPI: StripeAPI,
    method: string,
    description: string,
    schema: z.ZodObject<any, any, any, any, {[x: string]: any}>
  ) {
    super();

    this.stripeAPI = StripeAPI;
    this.method = method;
    this.name = method;
    this.description = description;
    this.schema = schema;
  }

  _call(
    arg: z.output<typeof this.schema>,
    _runManager?: CallbackManagerForToolRun,
    _parentConfig?: RunnableConfig
  ): Promise<any> {
    return this.stripeAPI.run(this.method, arg);
  }
}

export default StripeTool;

```

--------------------------------------------------------------------------------
/typescript/src/langchain/toolkit.ts:
--------------------------------------------------------------------------------

```typescript
import {BaseToolkit} from '@langchain/core/tools';
import StripeTool from './tool';
import StripeAPI from '../shared/api';
import tools from '../shared/tools';
import {isToolAllowed, type Configuration} from '../shared/configuration';

class StripeAgentToolkit implements BaseToolkit {
  private _stripe: StripeAPI;

  tools: StripeTool[];

  constructor({
    secretKey,
    configuration,
  }: {
    secretKey: string;
    configuration: Configuration;
  }) {
    this._stripe = new StripeAPI(secretKey, configuration.context);

    const context = configuration.context || {};
    const filteredTools = tools(context).filter((tool) =>
      isToolAllowed(tool, configuration)
    );

    this.tools = filteredTools.map(
      (tool) =>
        new StripeTool(
          this._stripe,
          tool.method,
          tool.description,
          tool.parameters
        )
    );
  }

  getTools(): StripeTool[] {
    return this.tools;
  }
}

export default StripeAgentToolkit;

```

--------------------------------------------------------------------------------
/python/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "stripe-agent-toolkit"
version = "0.6.1"
description = "Stripe Agent Toolkit"
readme = "README.md"
license = {file = "LICENSE"}
authors = [
  {name = "Stripe", email = "[email protected]"}
]
keywords = ["stripe", "api", "payments"]

[project.urls]
"Bug Tracker" = "https://github.com/stripe/agent-toolkit/issues"
"Source Code" = "https://github.com/stripe/agent-toolkit"

[tool.setuptools.packages.find]
include = ["stripe_agent_toolkit*"]
exclude = ["tests*", "examples*"]

[tool.ruff]
# same as our black config
line-length = 79
extend-exclude = ["build"]

[tool.ruff.format]
# currently the default value, but opt-out in the future
docstring-code-format = false

[tool.pyright]
include = [
  "*",
]
exclude = ["build", "**/__pycache__"]
reportMissingTypeArgument = true
reportUnnecessaryCast = true
reportUnnecessaryComparison = true
reportUnnecessaryContains = true
reportUnnecessaryIsInstance = true
reportPrivateImportUsage = true
reportUnnecessaryTypeIgnoreComment = true

```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------

```yaml
name: Feature request
description: Suggest an idea for this library
labels: ["feature-request"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this feature request!
  - type: textarea
    id: problem
    attributes:
      label: Is your feature request related to a problem? Please describe.
      description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
  - type: textarea
    id: solution
    attributes:
      label: Describe the solution you'd like
      description: A clear and concise description of what you want to happen.
  - type: textarea
    id: alternatives
    attributes:
      label: Describe alternatives you've considered
      description: A clear and concise description of any alternative solutions or features you've considered.
  - type: textarea
    id: context
    attributes:
      label: Additional context
      description: Add any other context about the feature request here.

```

--------------------------------------------------------------------------------
/python/examples/langchain/main.py:
--------------------------------------------------------------------------------

```python
import os
from dotenv import load_dotenv

from langchain import hub
from langchain_openai import ChatOpenAI

from langgraph.prebuilt import create_react_agent

from stripe_agent_toolkit.langchain.toolkit import StripeAgentToolkit

load_dotenv()

llm = ChatOpenAI(
    model="gpt-4o",
)

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=os.getenv("STRIPE_SECRET_KEY"),
    configuration={
        "actions": {
            "payment_links": {
                "create": True,
            },
            "products": {
                "create": True,
            },
            "prices": {
                "create": True,
            },
        }
    },
)

tools = []
tools.extend(stripe_agent_toolkit.get_tools())

langgraph_agent_executor = create_react_agent(llm, tools)

input_state = {
    "messages": """
        Create a payment link for a new product called 'test' with a price
        of $100. Come up with a funny description about buy bots,
        maybe a haiku.
    """,
}

output_state = langgraph_agent_executor.invoke(input_state)

print(output_state["messages"][-1].content)

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/crewai/toolkit.py:
--------------------------------------------------------------------------------

```python
"""Stripe Agent Toolkit."""

from typing import List, Optional
from pydantic import PrivateAttr

from ..api import StripeAPI
from ..tools import tools
from ..configuration import Configuration, is_tool_allowed
from .tool import StripeTool


class StripeAgentToolkit:
    _tools: List = PrivateAttr(default=[])

    def __init__(
        self, secret_key: str, configuration: Optional[Configuration] = None
    ):
        super().__init__()

        context = configuration.get("context") if configuration else None

        stripe_api = StripeAPI(secret_key=secret_key, context=context)

        filtered_tools = [
            tool for tool in tools if is_tool_allowed(tool, configuration)
        ]

        self._tools = [
            StripeTool(
                name=tool["method"],
                description=tool["description"],
                method=tool["method"],
                stripe_api=stripe_api,
                args_schema=tool.get("args_schema", None),
            )
            for tool in filtered_tools
        ]

    def get_tools(self) -> List:
        """Get the tools in the toolkit."""
        return self._tools

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/langchain/toolkit.py:
--------------------------------------------------------------------------------

```python
"""Stripe Agent Toolkit."""

from typing import List, Optional
from pydantic import PrivateAttr

from ..api import StripeAPI
from ..tools import tools
from ..configuration import Configuration, Context, is_tool_allowed
from .tool import StripeTool


class StripeAgentToolkit:
    _tools: List = PrivateAttr(default=[])

    def __init__(
        self, secret_key: str, configuration: Optional[Configuration] = None
    ):
        super().__init__()

        context = configuration.get("context") if configuration else None

        stripe_api = StripeAPI(secret_key=secret_key, context=context)

        filtered_tools = [
            tool for tool in tools if is_tool_allowed(tool, configuration)
        ]

        self._tools = [
            StripeTool(
                name=tool["method"],
                description=tool["description"],
                method=tool["method"],
                stripe_api=stripe_api,
                args_schema=tool.get("args_schema", None),
            )
            for tool in filtered_tools
        ]

    def get_tools(self) -> List:
        """Get the tools in the toolkit."""
        return self._tools

```

--------------------------------------------------------------------------------
/typescript/examples/ai-sdk/index.ts:
--------------------------------------------------------------------------------

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/ai-sdk';
import {openai} from '@ai-sdk/openai';
import {
  generateText,
  experimental_wrapLanguageModel as wrapLanguageModel,
} from 'ai';

require('dotenv').config();

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
      products: {
        create: true,
      },
      prices: {
        create: true,
      },
    },
  },
});

const model = wrapLanguageModel({
  model: openai('gpt-4o'),
  middleware: stripeAgentToolkit.middleware({
    billing: {
      customer: process.env.STRIPE_CUSTOMER_ID!,
      meters: {
        input: process.env.STRIPE_METER_INPUT!,
        output: process.env.STRIPE_METER_OUTPUT!,
      },
    },
  }),
});

(async () => {
  const result = await generateText({
    model: model,
    tools: {
      ...stripeAgentToolkit.getTools(),
    },
    maxSteps: 5,
    prompt:
      'Create a payment link for a new product called "test" with a price of $100. Come up with a funny description about buy bots, maybe a haiku.',
  });

  console.log(result);
})();

```

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

```python
"""Stripe Agent Toolkit."""

from typing import List, Optional, Dict
from strands.tools.tools import PythonAgentTool as StrandTool

from ..api import StripeAPI
from ..tools import tools
from ..configuration import Configuration, is_tool_allowed
from .tool import StripeTool
from .hooks import BillingHooks


class StripeAgentToolkit:
    def __init__(
        self, secret_key: str, configuration: Optional[Configuration] = None
    ):
        context = configuration.get("context") if configuration else None

        self._stripe_api = StripeAPI(secret_key=secret_key, context=context)

        filtered_tools = [
            tool for tool in tools if is_tool_allowed(tool, configuration)
        ]

        self._tools = [
            StripeTool(self._stripe_api, tool)
            for tool in filtered_tools
        ]

    def get_tools(self) -> List[StrandTool]:
        """Get the tools in the toolkit."""
        return self._tools

    def billing_hook(
        self,
        type: Optional[str] = None,
        customer: Optional[str] = None,
        meter: Optional[str] = None,
        meters: Optional[Dict[str, str]] = None
    ) -> BillingHooks:
        return BillingHooks(self._stripe_api, type, customer, meter, meters)

```

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

```typescript
import {retrieveBalance} from '@/shared/balance/retrieveBalance';

const Stripe = jest.fn().mockImplementation(() => ({
  balance: {
    retrieve: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('retrieveBalance', () => {
  it('should retrieve the balance and return it', async () => {
    const mockBalance = {available: [{amount: 1000, currency: 'usd'}]};

    const context = {};

    stripe.balance.retrieve.mockResolvedValue(mockBalance);

    const result = await retrieveBalance(stripe, context, {});

    expect(stripe.balance.retrieve).toHaveBeenCalledWith({}, undefined);
    expect(result).toEqual(mockBalance);
  });

  it('should specify the connected account if included in context', async () => {
    const mockBalance = {available: [{amount: 1000, currency: 'usd'}]};

    const context = {
      account: 'acct_123456',
    };

    stripe.balance.retrieve.mockResolvedValue(mockBalance);

    const result = await retrieveBalance(stripe, context, {});

    expect(stripe.balance.retrieve).toHaveBeenCalledWith(
      {},
      {
        stripeAccount: context.account,
      }
    );
    expect(result).toEqual(mockBalance);
  });
});

```

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

```typescript
import {createPaymentLinkPrompt} from '@/shared/paymentLinks/createPaymentLink';

describe('createPaymentLinkPrompt', () => {
  it('should return the correct prompt', () => {
    const prompt = createPaymentLinkPrompt({});

    expect(prompt).toContain('This tool will create a payment link in Stripe.');
    expect(prompt).toContain(
      'price (str): The ID of the price to create the payment link for.'
    );
    expect(prompt).toContain(
      'quantity (int): The quantity of the product to include in the payment link.'
    );
    expect(prompt).toContain(
      'redirect_url (str, optional): The URL to redirect to after the payment is completed.'
    );
  });

  it('should return the correct prompt with customer context', () => {
    const prompt = createPaymentLinkPrompt({customer: 'cus_123'});

    expect(prompt).toContain('This tool will create a payment link in Stripe.');
    expect(prompt).toContain(
      'price (str): The ID of the price to create the payment link for.'
    );
    expect(prompt).toContain(
      'quantity (int): The quantity of the product to include in the payment link.'
    );
    expect(prompt).toContain(
      'redirect_url (str, optional): The URL to redirect to after the payment is completed.'
    );
  });
});

```

--------------------------------------------------------------------------------
/typescript/examples/langchain/index.ts:
--------------------------------------------------------------------------------

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/langchain';
import {ChatOpenAI} from '@langchain/openai';
import type {ChatPromptTemplate} from '@langchain/core/prompts';
import {pull} from 'langchain/hub';
import {AgentExecutor, createStructuredChatAgent} from 'langchain/agents';

require('dotenv').config();

const llm = new ChatOpenAI({
  model: 'gpt-4o',
});

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
      products: {
        create: true,
      },
      prices: {
        create: true,
      },
    },
  },
});

(async (): Promise<void> => {
  const prompt = await pull<ChatPromptTemplate>(
    'hwchase17/structured-chat-agent'
  );

  const tools = stripeAgentToolkit.getTools();

  const agent = await createStructuredChatAgent({
    llm,
    tools,
    prompt,
  });

  const agentExecutor = new AgentExecutor({
    agent,
    tools,
  });

  const response = await agentExecutor.invoke({
    input: `
      Create a payment link for a new product called 'test' with a price
      of $100. Come up with a funny description about buy bots,
      maybe a haiku.
    `,
  });

  console.log(response);
})();

```

--------------------------------------------------------------------------------
/typescript/src/shared/balance/retrieveBalance.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const retrieveBalancePrompt = (_context: Context = {}) => `
This tool will retrieve the balance from Stripe. It takes no input.
`;

export const retrieveBalanceParameters = (
  _context: Context = {}
): z.AnyZodObject => z.object({});

export const retrieveBalanceAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'Retrieve balance',
});

export const retrieveBalance = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof retrieveBalanceParameters>>
) => {
  try {
    const balance = await stripe.balance.retrieve(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return balance;
  } catch (error) {
    return 'Failed to retrieve balance';
  }
};

const tool = (context: Context): Tool => ({
  method: 'retrieve_balance',
  name: 'Retrieve Balance',
  description: retrieveBalancePrompt(context),
  parameters: retrieveBalanceParameters(context),
  annotations: retrieveBalanceAnnotations(),
  actions: {
    balance: {
      read: true,
    },
  },
  execute: retrieveBalance,
});

export default tool;

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/openai/tool.py:
--------------------------------------------------------------------------------

```python
"""
This tool allows agents to interact with the Stripe API.
"""

from __future__ import annotations

from collections.abc import Awaitable
from typing import Any
import json

from agents import FunctionTool
from agents.run_context import RunContextWrapper

def StripeTool(api, tool) -> FunctionTool:
    async def on_invoke_tool(ctx: RunContextWrapper[Any], input_str: str) -> str:
        return api.run(tool["method"], **json.loads(input_str))

    parameters = tool["args_schema"].model_json_schema()
    parameters["additionalProperties"] = False
    parameters["type"] = "object"

    # Remove the description field from parameters as it's not needed in the OpenAI function schema
    if "description" in parameters:
        del parameters["description"]

    if "title" in parameters:
        del parameters["title"]

    # Remove title and default fields from properties
    if "properties" in parameters:
        for prop in parameters["properties"].values():
            if "title" in prop:
                del prop["title"]
            if "default" in prop:
                del prop["default"]

    return FunctionTool(
        name=tool["method"],
        description=tool["description"],
        params_json_schema=parameters,
        on_invoke_tool=on_invoke_tool,
        strict_json_schema=False
    )

```

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

```typescript
import {createProductParameters} from '@/shared/products/createProduct';
import {listProductsParameters} from '@/shared/products/listProducts';

describe('createProductParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createProductParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['name', 'description']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createProductParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['name', 'description']);
    expect(fields.length).toBe(2);
  });
});

describe('listProductsParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listProductsParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit']);
    expect(fields.length).toBe(1);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listProductsParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit']);
    expect(fields.length).toBe(1);
  });
});

```

--------------------------------------------------------------------------------
/.github/workflows/pypi_release.yml:
--------------------------------------------------------------------------------

```yaml
name: Python Release

on:
  workflow_dispatch: {}

jobs:
  python-build:
    name: Build for PyPi
    runs-on: ubuntu-latest
    environment: pypi

    defaults:
      run:
        working-directory: ./python

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

      - name: Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install
        run: make venv

      - name: Build
        run: |
          set -x
          source venv/bin/activate
          rm -rf build dist *.egg-info
          make build
          python -m twine check dist/*

      - name: Test
        run: |
          make venv
          make test

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: release-dists
          path: ./python/dist/

  python-release:
    name: Publish to PyPi
    runs-on: ubuntu-latest
    environment: pypi
    needs:
      - python-build

    defaults:
      run:
        working-directory: ./python

    permissions:
      id-token: write

    steps:
      - name: Retrieve distribution
        uses: actions/download-artifact@v4
        with:
          name: release-dists
          path: dist/

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/openai/toolkit.py:
--------------------------------------------------------------------------------

```python
"""Stripe Agent Toolkit."""

from typing import List, Optional
from pydantic import PrivateAttr
import json

from agents import FunctionTool


from ..api import StripeAPI
from ..tools import tools
from ..configuration import Configuration, is_tool_allowed
from .tool import StripeTool
from .hooks import BillingHooks

class StripeAgentToolkit:
    _tools: List[FunctionTool] = PrivateAttr(default=[])
    _stripe_api: StripeAPI = PrivateAttr(default=None)

    def __init__(
        self, secret_key: str, configuration: Optional[Configuration] = None
    ):
        super().__init__()

        context = configuration.get("context") if configuration else None

        self._stripe_api = StripeAPI(secret_key=secret_key, context=context)

        filtered_tools = [
            tool for tool in tools if is_tool_allowed(tool, configuration)
        ]

        self._tools = [
            StripeTool(self._stripe_api, tool)
            for tool in filtered_tools
        ]

    def get_tools(self) -> List[FunctionTool]:
        """Get the tools in the toolkit."""
        return self._tools

    def billing_hook(self, type: Optional[str] = None, customer: Optional[str] = None, meter: Optional[str] = None, meters: Optional[dict[str, str]] = None) -> BillingHooks:
        return BillingHooks(self._stripe_api, type, customer, meter, meters)

```

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

```typescript
import {createPriceParameters} from '@/shared/prices/createPrice';
import {listPricesParameters} from '@/shared/prices/listPrices';

describe('createPriceParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createPriceParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['product', 'unit_amount', 'currency']);
    expect(fields.length).toBe(3);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createPriceParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['product', 'unit_amount', 'currency']);
    expect(fields.length).toBe(3);
  });
});

describe('listPricesParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listPricesParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['product', 'limit']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listPricesParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['product', 'limit']);
    expect(fields.length).toBe(2);
  });
});

```

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

```typescript
import {createInvoicePrompt} from '@/shared/invoices/createInvoice';
import {listInvoicesPrompt} from '@/shared/invoices/listInvoices';
import {finalizeInvoicePrompt} from '@/shared/invoices/finalizeInvoice';

describe('createInvoicePrompt', () => {
  it('should return the correct prompt when no customer is specified', () => {
    const prompt = createInvoicePrompt({});
    expect(prompt).toContain('- customer (str)');
  });

  it('should return the correct prompt when a customer is specified', () => {
    const prompt = createInvoicePrompt({customer: 'cus_123'});
    expect(prompt).toContain('context: cus_123');
    expect(prompt).not.toContain('- customer (str)');
  });
});

describe('listInvoicesPrompt', () => {
  it('should return the correct prompt when no customer is specified', () => {
    const prompt = listInvoicesPrompt({});
    expect(prompt).toContain('- customer (str, optional)');
  });

  it('should return the correct prompt when a customer is specified', () => {
    const prompt = listInvoicesPrompt({customer: 'cus_123'});
    expect(prompt).toContain('context: cus_123');
    expect(prompt).not.toContain('- customer (str, optional)');
  });
});

describe('finalizeInvoicePrompt', () => {
  it('should return the correct prompt', () => {
    const prompt = finalizeInvoicePrompt();
    expect(prompt).toContain('invoice');
  });
});

```

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

```python
import env
from agents import Agent, Runner, function_tool, TResponseInputItem, RunResult
from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit
import requests

env.ensure("OPENAI_API_KEY")

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=env.ensure("STRIPE_SECRET_KEY"),
    configuration={
        "actions": {
            "customers": {
                "read": True,
            },
            "invoices": {
                "read": True,
            },
            "billing_portal_sessions": {
                "create": True,
            },
        }
    },
)


@function_tool
def search_faq(question: str) -> str:
    response = requests.get("https://standupjack.com/faq")
    if response.status_code != 200:
        return "Not sure"
    return f"Given the following context:\n{response.text}\n\nAnswer '{question}' or response with not sure\n"


support_agent = Agent(
    name="Standup Jack Agent",
    instructions=(
        "You are a helpful customer support assistant"
        "Be casual and concise"
        "You only respond with markdown"
        "Use tools to support customers"
        "Respond with I'm not sure to any other prompts"
        "Sign off with Standup Jack Bot"
    ),
    tools=[search_faq, *stripe_agent_toolkit.get_tools()],
)


async def run(input: list[TResponseInputItem]) -> RunResult:
    return await Runner.run(support_agent, input)

```

--------------------------------------------------------------------------------
/typescript/examples/openai/index.ts:
--------------------------------------------------------------------------------

```typescript
import {StripeAgentToolkit} from '@stripe/agent-toolkit/openai';
import OpenAI from 'openai';
import type {ChatCompletionMessageParam} from 'openai/resources';

require('dotenv').config();

const openai = new OpenAI();

const stripeAgentToolkit = new StripeAgentToolkit({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  configuration: {
    actions: {
      paymentLinks: {
        create: true,
      },
      products: {
        create: true,
      },
      prices: {
        create: true,
      },
    },
  },
});

(async (): Promise<void> => {
  let messages: ChatCompletionMessageParam[] = [
    {
      role: 'user',
      content: `Create a payment link for a new product called 'test' with a price
of $100. Come up with a funny description about buy bots,
maybe a haiku.`,
    },
  ];

  while (true) {
    // eslint-disable-next-line no-await-in-loop
    const completion = await openai.chat.completions.create({
      model: 'gpt-4o',
      messages,
      tools: stripeAgentToolkit.getTools(),
    });

    const message = completion.choices[0].message;

    messages.push(message);

    if (message.tool_calls) {
      // eslint-disable-next-line no-await-in-loop
      const toolMessages = await Promise.all(
        message.tool_calls.map((tc) => stripeAgentToolkit.handleToolCall(tc))
      );
      messages = [...messages, ...toolMessages];
    } else {
      console.log(completion.choices[0].message);
      break;
    }
  }
})();

```

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

```typescript
import {createCustomerParameters} from '@/shared/customers/createCustomer';
import {listCustomersParameters} from '@/shared/customers/listCustomers';

describe('createCustomerParameters', () => {
  it('should return the correct parameters if no context', () => {
    // Create the parameters schema with an empty context
    const parameters = createCustomerParameters({});

    // Validate that the schema has the expected keys
    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['name', 'email']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createCustomerParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['name', 'email']);
    expect(fields.length).toBe(2);
  });
});

describe('listCustomersParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listCustomersParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit', 'email']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listCustomersParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit', 'email']);
    expect(fields.length).toBe(2);
  });
});

```

--------------------------------------------------------------------------------
/python/examples/crewai/main.py:
--------------------------------------------------------------------------------

```python
import os
from dotenv import load_dotenv

from crewai import Agent, Task, Crew
from stripe_agent_toolkit.crewai.toolkit import StripeAgentToolkit

load_dotenv()

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=os.getenv("STRIPE_SECRET_KEY"),
    configuration={
        "actions": {
            "payment_links": {
                "create": True,
            },
            "products": {
                "create": True,
            },
            "prices": {
                "create": True,
            },
        }
    },
)

stripe_agent = Agent(
    role="Stripe Agent",
    goal="Integrate with Stripe effectively to support our business.",
    backstory="You have been using stripe forever.",
    tools=[*stripe_agent_toolkit.get_tools()],
    allow_delegation=False,
    verbose=True,
)

haiku_writer = Agent(
    role="Haiku writer",
    goal="Write a haiku",
    backstory="You are really good at writing haikus.",
    allow_delegation=False,
    verbose=True,
)

create_payment_link = Task(
    description="Create a payment link for a new product called 'test' "
    "with a price of $100. The description should be a haiku",
    expected_output="url",
    agent=stripe_agent,
)

write_haiku = Task(
    description="Write a haiku about buy bots.",
    expected_output="haiku",
    agent=haiku_writer,
)

crew = Crew(
    agents=[stripe_agent, haiku_writer],
    tasks=[create_payment_link, write_haiku],
    verbose=True,
    planning=True,
)

crew.kickoff()

```

--------------------------------------------------------------------------------
/typescript/src/modelcontextprotocol/toolkit.ts:
--------------------------------------------------------------------------------

```typescript
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {RequestHandlerExtra} from '@modelcontextprotocol/sdk/shared/protocol.js';
import {Configuration, isToolAllowed} from '../shared/configuration';
import StripeAPI from '../shared/api';
import tools from '../shared/tools';

class StripeAgentToolkit extends McpServer {
  private _stripe: StripeAPI;

  constructor({
    secretKey,
    configuration,
  }: {
    secretKey: string;
    configuration: Configuration;
  }) {
    super({
      name: 'Stripe',
      version: '0.4.0',
      configuration: {
        ...configuration,
        context: {
          ...configuration.context,
          mode: 'modelcontextprotocol',
        },
      },
    });

    this._stripe = new StripeAPI(secretKey, configuration.context);

    const context = configuration.context || {};
    const filteredTools = tools(context).filter((tool) =>
      isToolAllowed(tool, configuration)
    );

    filteredTools.forEach((tool) => {
      this.tool(
        tool.method,
        tool.description,
        tool.parameters.shape,
        tool.annotations,
        async (arg: any, _extra: RequestHandlerExtra<any, any>) => {
          const result = await this._stripe.run(tool.method, arg);
          return {
            content: [
              {
                type: 'text' as const,
                text: String(result),
              },
            ],
          };
        }
      );
    });
  }
}

export default StripeAgentToolkit;

```

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

```typescript
import Stripe from 'stripe';

import type {Context} from './configuration';
import tools, {Tool} from './tools';

const TOOLKIT_HEADER = 'stripe-agent-toolkit-typescript';
const MCP_HEADER = 'stripe-mcp';

class StripeAPI {
  stripe: Stripe;

  context: Context;

  tools: Tool[];

  constructor(secretKey: string, context?: Context) {
    const stripeClient = new Stripe(secretKey, {
      appInfo: {
        name:
          context?.mode === 'modelcontextprotocol'
            ? MCP_HEADER
            : TOOLKIT_HEADER,
        version: '0.7.11',
        url: 'https://github.com/stripe/agent-toolkit',
      },
    });
    this.stripe = stripeClient;
    this.context = context || {};
    this.tools = tools(this.context);
  }

  async createMeterEvent({
    event,
    customer,
    value,
  }: {
    event: string;
    customer: string;
    value: string;
  }) {
    await this.stripe.billing.meterEvents.create(
      {
        event_name: event,
        payload: {
          stripe_customer_id: customer,
          value: value,
        },
      },
      this.context.account ? {stripeAccount: this.context.account} : undefined
    );
  }

  async run(method: string, arg: any) {
    const tool = this.tools.find((t) => t.method === method);
    if (tool) {
      const output = JSON.stringify(
        await tool.execute(this.stripe, this.context, arg)
      );
      return output;
    } else {
      throw new Error('Invalid method ' + method);
    }
  }
}

export default StripeAPI;

```

--------------------------------------------------------------------------------
/typescript/src/shared/products/createProduct.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';
export const createProductPrompt = (_context: Context = {}) => `
This tool will create a product in Stripe.

It takes two arguments:
- name (str): The name of the product.
- description (str, optional): The description of the product.
`;

export const createProduct = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createProductParameters>>
) => {
  try {
    const product = await stripe.products.create(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return product;
  } catch (error) {
    return 'Failed to create product';
  }
};

export const createProductParameters = (_context: Context = {}) =>
  z.object({
    name: z.string().describe('The name of the product.'),
    description: z
      .string()
      .optional()
      .describe('The description of the product.'),
  });

export const createProductAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create product',
});

const tool = (context: Context): Tool => ({
  method: 'create_product',
  name: 'Create Product',
  description: createProductPrompt(context),
  parameters: createProductParameters(context),
  annotations: createProductAnnotations(),
  actions: {
    products: {
      create: true,
    },
  },
  execute: createProduct,
});

export default tool;

```

--------------------------------------------------------------------------------
/python/tests/test_configuration.py:
--------------------------------------------------------------------------------

```python
import unittest
from stripe_agent_toolkit.configuration import is_tool_allowed


class TestConfigurations(unittest.TestCase):
    def test_allowed(self):
        tool = {
            "actions": {
                "customers": {"create": True, "read": True},
                "invoices": {"create": True, "read": True},
            }
        }

        configuration = {
            "actions": {
                "customers": {"create": True, "read": True},
                "invoices": {"create": True, "read": True},
            }
        }

        self.assertTrue(is_tool_allowed(tool, configuration))

    def test_partial_allowed(self):
        tool = {
            "actions": {
                "customers": {"create": True, "read": True},
                "invoices": {"create": True, "read": True},
            }
        }

        configuration = {
            "actions": {
                "customers": {"create": True, "read": True},
                "invoices": {"create": True, "read": False},
            }
        }

        self.assertFalse(is_tool_allowed(tool, configuration))

    def test_not_allowed(self):
        tool = {
            "actions": {
                "payment_links": {"create": True},
            }
        }

        configuration = {
            "actions": {
                "customers": {"create": True, "read": True},
                "invoices": {"create": True, "read": True},
            }
        }

        self.assertFalse(is_tool_allowed(tool, configuration))


if __name__ == "__main__":
    unittest.main()

```

--------------------------------------------------------------------------------
/typescript/src/shared/customers/createCustomer.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createCustomerPrompt = (_context: Context = {}) => `
This tool will create a customer in Stripe.

It takes two arguments:
- name (str): The name of the customer.
- email (str, optional): The email of the customer.
`;

export const createCustomerParameters = (
  _context: Context = {}
): z.AnyZodObject =>
  z.object({
    name: z.string().describe('The name of the customer'),
    email: z.string().email().optional().describe('The email of the customer'),
  });

export const createCustomerAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create customer',
});

export const createCustomer = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createCustomerParameters>>
) => {
  try {
    const customer = await stripe.customers.create(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {id: customer.id};
  } catch (error) {
    return 'Failed to create customer';
  }
};

const tool = (context: Context): Tool => ({
  method: 'create_customer',
  name: 'Create Customer',
  description: createCustomerPrompt(context),
  parameters: createCustomerParameters(context),
  annotations: createCustomerAnnotations(),
  actions: {
    customers: {
      create: true,
    },
  },
  execute: createCustomer,
});

export default tool;

```

--------------------------------------------------------------------------------
/modelcontextprotocol/build-dxt.js:
--------------------------------------------------------------------------------

```javascript
/* eslint-disable no-sync */
const esbuild = require('esbuild');
const fsSync = require('fs');

const dxt = require('@anthropic-ai/dxt');

// Ensure dist directory exists
if (!fsSync.existsSync('dxt-dist')) {
  fsSync.mkdirSync('dxt-dist');
}

// Build configuration
const buildConfig = {
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dxt-dist/index.js',
  platform: 'node',
  target: 'node18',
  format: 'cjs',
  external: [],
  minify: true,
  sourcemap: false,
  metafile: false,
  write: true,
  logLevel: 'info',
};

async function build() {
  try {
    console.log('🔨 Building with esbuild...');

    const result = await esbuild.build(buildConfig);

    if (result.errors.length > 0) {
      console.error('❌ Build failed with errors:');
      result.errors.forEach((error) => console.error(error));
      throw new Error('Build failed with errors');
    }

    if (result.warnings.length > 0) {
      console.warn('⚠️  Build completed with warnings:');
      result.warnings.forEach((warning) => console.warn(warning));
    }

    // Make the output file executable
    fsSync.chmodSync('dxt-dist/index.js', '755');

    console.log('✅ Build completed successfully!');
    console.log('📦 Output: dxt-dist/index.js');
  } catch (error) {
    console.error('❌ Build failed:', error);
    throw error;
  }
}

// Run the build
build();

// Pack the actual DXT extension
dxt.packExtension({
  extensionPath: '.',
  outputPath: 'stripe.dxt',
  silent: true,
});

console.log('✅ DXT extension built successfully!');
console.log('📦 Output: stripe.dxt');

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const finalizeInvoiceParameters = (
  _context: Context = {}
): z.AnyZodObject =>
  z.object({
    invoice: z.string().describe('The ID of the invoice to finalize.'),
  });

export const finalizeInvoicePrompt = (_context: Context = {}) => `
This tool will finalize an invoice in Stripe.

It takes one argument:
- invoice (str): The ID of the invoice to finalize.
`;

export const finalizeInvoice = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof finalizeInvoiceParameters>>
) => {
  try {
    const invoice = await stripe.invoices.finalizeInvoice(
      params.invoice,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {
      id: invoice.id,
      url: invoice.hosted_invoice_url,
      customer: invoice.customer,
      status: invoice.status,
    };
  } catch (error) {
    return 'Failed to finalize invoice';
  }
};

export const finalizeInvoiceAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Finalize invoice',
});

const tool = (context: Context): Tool => ({
  method: 'finalize_invoice',
  name: 'Finalize Invoice',
  description: finalizeInvoicePrompt(context),
  parameters: finalizeInvoiceParameters(context),
  annotations: finalizeInvoiceAnnotations(),
  actions: {
    invoices: {
      update: true,
    },
  },
  execute: finalizeInvoice,
});

export default tool;

```

--------------------------------------------------------------------------------
/typescript/src/shared/products/listProducts.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listProductsPrompt = (_context: Context = {}) => `
This tool will fetch a list of Products from Stripe.

It takes one optional argument:
- limit (int, optional): The number of products to return.
`;

export const listProducts = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listProductsParameters>>
) => {
  try {
    const products = await stripe.products.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return products.data;
  } catch (error) {
    return 'Failed to list products';
  }
};

export const listProductsParameters = (
  _context: Context = {}
): z.AnyZodObject =>
  z.object({
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
      ),
  });

export const listProductsAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List products',
});

const tool = (context: Context): Tool => ({
  method: 'list_products',
  name: 'List Products',
  description: listProductsPrompt(context),
  parameters: listProductsParameters(context),
  annotations: listProductsAnnotations(),
  actions: {
    products: {
      read: true,
    },
  },
  execute: listProducts,
});

export default tool;

```

--------------------------------------------------------------------------------
/modelcontextprotocol/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@stripe/mcp",
  "version": "0.2.4",
  "homepage": "https://github.com/stripe/agent-toolkit/tree/main/modelcontextprotocol",
  "description": "A command line tool for setting up Stripe MCP server",
  "bin": "dist/index.js",
  "files": [
    "dist/index.js",
    "LICENSE",
    "README.md",
    "VERSION",
    "package.json"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
    "clean": "rm -rf dist",
    "lint": "eslint \"./**/*.ts*\"",
    "prettier": "prettier './**/*.{js,ts,md,html,css}' --write",
    "prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check",
    "test": "jest",
    "build-dxt-extension": "node build-dxt.js"
  },
  "packageManager": "[email protected]",
  "engines": {
    "node": ">=18"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.17.1",
    "@stripe/agent-toolkit": "^0.7.11",
    "colors": "^1.4.0"
  },
  "keywords": [
    "mcp",
    "modelcontextprotocol",
    "stripe"
  ],
  "author": "Stripe <[email protected]> (https://stripe.com/)",
  "license": "MIT",
  "devDependencies": {
    "@anthropic-ai/dxt": "^0.1.0",
    "@eslint/compat": "^1.2.6",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.13.4",
    "@typescript-eslint/eslint-plugin": "^8.24.1",
    "esbuild": "^0.25.5",
    "eslint-config-prettier": "^10.0.1",
    "eslint-plugin-import": "^2.31.0",
    "eslint-plugin-jest": "^28.11.0",
    "eslint-plugin-prettier": "^5.2.3",
    "globals": "^15.15.0",
    "jest": "^29.7.0",
    "prettier": "^3.5.1",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
}

```

--------------------------------------------------------------------------------
/python/stripe_agent_toolkit/configuration.py:
--------------------------------------------------------------------------------

```python
from typing import Literal, Optional
from typing_extensions import TypedDict

# Define Object type
Object = Literal[
    "customers",
    "invoices",
    "invoiceItems",
    "paymentLinks",
    "products",
    "prices",
    "balance",
    "refunds",
    "paymentIntents",
]


# Define Permission type
class Permission(TypedDict, total=False):
    create: Optional[bool]
    update: Optional[bool]
    read: Optional[bool]


# Define BalancePermission type
class BalancePermission(TypedDict, total=False):
    read: Optional[bool]


# Define Actions type
class Actions(TypedDict, total=False):
    customers: Optional[Permission]
    invoices: Optional[Permission]
    invoice_items: Optional[Permission]
    payment_links: Optional[Permission]
    products: Optional[Permission]
    prices: Optional[Permission]
    balance: Optional[BalancePermission]
    refunds: Optional[Permission]
    payment_intents: Optional[Permission]
    billing_portal_sessions: Optional[Permission]


# Define Context type
class Context(TypedDict, total=False):
    account: Optional[str]


# Define Configuration type
class Configuration(TypedDict, total=False):
    actions: Optional[Actions]
    context: Optional[Context]


def is_tool_allowed(tool, configuration):
    for resource, permissions in tool.get("actions", {}).items():
        if resource not in configuration.get("actions", {}):
            return False
        for permission in permissions:
            if (
                not configuration["actions"]
                .get(resource, {})
                .get(permission, False)
            ):
                return False
    return True

```

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

```typescript
import {z, ZodRawShape} from 'zod';
import {ToolCallback} from '@modelcontextprotocol/sdk/server/mcp.js';
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {registerPaidTool} from '../modelcontextprotocol/register-paid-tool';
import type {PaidToolOptions} from '../modelcontextprotocol/register-paid-tool';

// @ts-ignore: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'.
import {McpAgent} from 'agents/mcp';

type Env = any;

type StripeState = {
  customerId: string;
};

export type PaymentState = {
  stripe?: StripeState;
};

export type PaymentProps = {
  userEmail: string;
};

// eslint-disable-next-line @typescript-eslint/naming-convention
export abstract class experimental_PaidMcpAgent<
  Bindings extends Env,
  State extends PaymentState,
  Props extends PaymentProps,
> extends McpAgent<Bindings, State, Props> {
  paidTool<Args extends ZodRawShape>(
    toolName: string,
    toolDescription: string,
    paramsSchema: Args,
    // @ts-ignore
    paidCallback: ToolCallback<Args>,
    options: Omit<PaidToolOptions, 'userEmail' | 'stripeSecretKey'>
  ) {
    const mcpServer: McpServer = this.server as unknown as McpServer;

    const userEmail = this.props.userEmail;

    const updatedOptions = {
      ...options,
      userEmail,
      // @ts-ignore
      stripeSecretKey: this.env.STRIPE_SECRET_KEY,
    };

    // @ts-ignore
    registerPaidTool(
      mcpServer,
      toolName,
      toolDescription,
      paramsSchema,
      paidCallback,
      updatedOptions
    );
  }
}

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const cancelSubscription = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof cancelSubscriptionParameters>>
) => {
  try {
    const {subscription: subscriptionId, ...cancelParams} = params;

    const subscription = await stripe.subscriptions.cancel(
      subscriptionId,
      cancelParams,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return subscription;
  } catch (error) {
    return 'Failed to cancel subscription';
  }
};

export const cancelSubscriptionParameters = (
  _context: Context = {}
): z.AnyZodObject => {
  return z.object({
    subscription: z.string().describe('The ID of the subscription to cancel.'),
  });
};
export const cancelSubscriptionPrompt = (_context: Context = {}): string => {
  return `
This tool will cancel a subscription in Stripe.

It takes the following arguments:
- subscription (str, required): The ID of the subscription to cancel.
`;
};

export const cancelSubscriptionAnnotations = () => ({
  destructiveHint: true,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Cancel subscription',
});

const tool = (context: Context): Tool => ({
  method: 'cancel_subscription',
  name: 'Cancel Subscription',
  description: cancelSubscriptionPrompt(context),
  parameters: cancelSubscriptionParameters(context),
  annotations: cancelSubscriptionAnnotations(),
  actions: {
    subscriptions: {
      update: true,
    },
  },
  execute: cancelSubscription,
});

export default tool;

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listCouponsPrompt = (_context: Context = {}) => `
This tool will fetch a list of Coupons from Stripe.

It takes one optional argument:
- limit (int, optional): The number of coupons to return.
`;

export const listCouponsParameters = (_context: Context = {}) =>
  z.object({
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
      ),
  });

export const listCouponsAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List coupons',
});

export const listCoupons = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listCouponsParameters>>
) => {
  try {
    const coupons = await stripe.coupons.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return coupons.data.map((coupon) => ({
      id: coupon.id,
      name: coupon.name,
      percent_off: coupon.percent_off,
      amount_off: coupon.amount_off,
      duration: coupon.duration,
    }));
  } catch (error) {
    return 'Failed to list coupons';
  }
};

const tool = (context: Context): Tool => ({
  method: 'list_coupons',
  name: 'List Coupons',
  description: listCouponsPrompt(context),
  parameters: listCouponsParameters(context),
  annotations: listCouponsAnnotations(),
  actions: {
    coupons: {
      read: true,
    },
  },
  execute: listCoupons,
});

export default tool;

```

--------------------------------------------------------------------------------
/typescript/src/shared/prices/listPrices.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listPrices = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listPricesParameters>>
) => {
  try {
    const prices = await stripe.prices.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return prices.data;
  } catch (error) {
    return 'Failed to list prices';
  }
};

export const listPricesParameters = (_context: Context = {}): z.AnyZodObject =>
  z.object({
    product: z
      .string()
      .optional()
      .describe('The ID of the product to list prices for.'),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
      ),
  });

export const listPricesAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List prices',
});

export const listPricesPrompt = (_context: Context = {}) => `
This tool will fetch a list of Prices from Stripe.

It takes two arguments.
- product (str, optional): The ID of the product to list prices for.
- limit (int, optional): The number of prices to return.
`;

const tool = (context: Context): Tool => ({
  method: 'list_prices',
  name: 'List Prices',
  description: listPricesPrompt(context),
  parameters: listPricesParameters(context),
  annotations: listPricesAnnotations(),
  actions: {
    prices: {
      read: true,
    },
  },
  execute: listPrices,
});

export default tool;

```

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

```typescript
import {createRefund} from '@/shared/refunds/createRefund';

const Stripe = jest.fn().mockImplementation(() => ({
  refunds: {
    create: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createRefund', () => {
  it('should create a refund and return it', async () => {
    const params = {
      payment_intent: 'pi_123456',
    };

    const mockRefund = {id: 're_123456'};

    const context = {};

    stripe.refunds.create.mockResolvedValue(mockRefund);

    const result = await createRefund(stripe, context, params);

    expect(stripe.refunds.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockRefund);
  });

  it('should create a partial refund and return it', async () => {
    const params = {
      payment_intent: 'pi_123456',
      amount: 500,
    };

    const mockRefund = {id: 're_123456'};

    const context = {};

    stripe.refunds.create.mockResolvedValue(mockRefund);

    const result = await createRefund(stripe, context, params);

    expect(stripe.refunds.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockRefund);
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      payment_intent: 'pi_123456',
    };

    const mockRefund = {id: 're_123456'};

    const context = {
      account: 'acct_123456',
    };

    stripe.refunds.create.mockResolvedValue(mockRefund);

    const result = await createRefund(stripe, context, params);

    expect(stripe.refunds.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockRefund);
  });
});

```

--------------------------------------------------------------------------------
/typescript/src/shared/refunds/createRefund.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createRefundPrompt = (_context: Context = {}) => `
This tool will refund a payment intent in Stripe.

It takes three arguments:
- payment_intent (str): The ID of the payment intent to refund.
- amount (int, optional): The amount to refund in cents.
- reason (str, optional): The reason for the refund.
`;

export const createRefund = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createRefundParameters>>
) => {
  try {
    const refund = await stripe.refunds.create(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {
      id: refund.id,
      status: refund.status,
      amount: refund.amount,
    };
  } catch (error) {
    return 'Failed to create refund';
  }
};

export const createRefundParameters = (
  _context: Context = {}
): z.AnyZodObject =>
  z.object({
    payment_intent: z
      .string()
      .describe('The ID of the PaymentIntent to refund.'),
    amount: z
      .number()
      .int()
      .optional()
      .describe('The amount to refund in cents.'),
  });

export const createRefundAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create refund',
});

const tool = (context: Context): Tool => ({
  method: 'create_refund',
  name: 'Create Refund',
  description: createRefundPrompt(context),
  parameters: createRefundParameters(context),
  annotations: createRefundAnnotations(),
  actions: {
    refunds: {
      create: true,
    },
  },
  execute: createRefund,
});

export default tool;

```

--------------------------------------------------------------------------------
/typescript/src/shared/prices/createPrice.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createPricePrompt = (_context: Context = {}) => `
This tool will create a price in Stripe. If a product has not already been specified, a product should be created first.

It takes three arguments:
- product (str): The ID of the product to create the price for.
- unit_amount (int): The unit amount of the price in cents.
- currency (str): The currency of the price.
`;

export const createPrice = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createPriceParameters>>
) => {
  try {
    const price = await stripe.prices.create(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return price;
  } catch (error) {
    return 'Failed to create price';
  }
};

export const createPriceParameters = (_context: Context = {}) =>
  z.object({
    product: z
      .string()
      .describe('The ID of the product to create the price for.'),
    unit_amount: z
      .number()
      .int()
      .describe('The unit amount of the price in cents.'),
    currency: z.string().describe('The currency of the price.'),
  });

export const createPriceAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create price',
});

const tool = (context: Context): Tool => ({
  method: 'create_price',
  name: 'Create Price',
  description: createPricePrompt(context),
  parameters: createPriceParameters(context),
  annotations: createPriceAnnotations(),
  actions: {
    prices: {
      create: true,
    },
  },
  execute: createPrice,
});

export default tool;

```

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

```python
from strands.tools.tools import PythonAgentTool as StrandTool
import json

def StripeTool(api, tool) -> StrandTool:
    parameters = tool["args_schema"].model_json_schema()
    parameters["additionalProperties"] = False
    parameters["type"] = "object"

    def callback_wrapper(tool_input, **kwargs):
        """Wrapper to handle additional parameters from strands framework."""

        # Extract toolUseId for the response
        tool_use_id = None
        if isinstance(tool_input, dict) and 'toolUseId' in tool_input:
            tool_use_id = tool_input['toolUseId']
            # Extract the actual parameters from the nested input structure
            actual_params = tool_input.get('input', {})
        elif isinstance(tool_input, str):
            # Parse JSON string input
            try:
                parsed = json.loads(tool_input)
                tool_use_id = parsed.get('toolUseId')
                actual_params = parsed.get('input', parsed)
            except json.JSONDecodeError:
                actual_params = {}
        elif isinstance(tool_input, dict):
            actual_params = tool_input.copy()
        else:
            actual_params = {}

        # Call the Stripe API
        result = api.run(tool["method"], **actual_params)

        # Return in the format expected by strands
        response = {
            "content": [{"text": result}]
        }

        if tool_use_id:
            response["toolUseId"] = tool_use_id

        return response

    return StrandTool(
        tool_name=tool["method"],
        tool_spec={
            "name": tool["method"],
            "description": tool["description"],
            "inputSchema": {
                "json": parameters
            }
        },
        callback=callback_wrapper
    )

```

--------------------------------------------------------------------------------
/modelcontextprotocol/manifest.json:
--------------------------------------------------------------------------------

```json
{
  "dxt_version": "0.1",
  "name": "@stripe/mcp",
  "display_name": "Stripe",
  "version": "0.1.0",
  "description": "Manage resources in your Stripe account and search the Stripe knowledge base.",
  "author": {
    "name": "Stripe",
    "email": "[email protected]"
  },
  "documentation": "https://docs.stripe.com/mcp",
  "user_config": {
    "stripe_secret_key": {
      "type": "string",
      "title": "Stripe key",
      "description": "Your Stripe API key. We recommend using a restricted access key.",
      "multiple": false,
      "required": true
    }
  },
  "server": {
    "type": "node",
    "entry_point": "dxt-dist/index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/dxt-dist/index.js", "--tools=all"],
      "env": {
        "STRIPE_SECRET_KEY": "${user_config.stripe_secret_key}"
      }
    }
  },
  "tools": [
    {"name": "search_documentation"},
    {"name": "get_stripe_account_in"},
    {"name": "create_customer"},
    {"name": "list_customers"},
    {"name": "create_product"},
    {"name": "list_products"},
    {"name": "create_price"},
    {"name": "list_prices"},
    {"name": "create_payment_link"},
    {"name": "create_invoice"},
    {"name": "list_invoices"},
    {"name": "create_invoice_item"},
    {"name": "finalize_invoice"},
    {"name": "retrieve_balance"},
    {"name": "create_refund"},
    {"name": "list_payment_intents"},
    {"name": "list_subscriptions"},
    {"name": "cancel_subscription"},
    {"name": "update_subscription"},
    {"name": "list_coupons"},
    {"name": "create_coupon"},
    {"name": "update_dispute"},
    {"name": "list_disputes"}
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/stripe/agent-toolkit/tree/main"
  },
  "icon": "stripe_icon.png"
}

```

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

```typescript
import type {Tool} from './tools';

// Actions restrict the subset of API calls that can be made. They should
// be used in conjunction with Restricted API Keys. Setting a permission to false
// prevents the related "tool" from being considered.
export type Object =
  | 'customers'
  | 'disputes'
  | 'invoices'
  | 'invoiceItems'
  | 'paymentLinks'
  | 'products'
  | 'prices'
  | 'balance'
  | 'refunds'
  | 'paymentIntents'
  | 'subscriptions'
  | 'documentation'
  | 'coupons';

export type Permission = 'create' | 'update' | 'read';

export type Actions = {
  [K in Object]?: {
    [K in Permission]?: boolean;
  };
} & {
  balance?: {
    read?: boolean;
  };
};

// Context are settings that are applied to all requests made by the integration.
export type Context = {
  // Account is a Stripe Connected Account ID. If set, the integration will
  // make requests for this Account.
  account?: string;

  // Customer is a Stripe Customer ID. If set, the integration will
  // make requests for this Customer.
  customer?: string;

  // If set to 'modelcontextprotocol', the Stripe API calls will use a special
  // header
  mode?: 'modelcontextprotocol' | 'toolkit';
};

// Configuration provides various settings and options for the integration
// to tune and manage how it behaves.
export type Configuration = {
  actions?: Actions;
  context?: Context;
};

export const isToolAllowed = (
  tool: Tool,
  configuration: Configuration
): boolean => {
  return Object.keys(tool.actions).every((resource) => {
    // For each resource.permission pair, check the configuration.
    // @ts-ignore
    const permissions = tool.actions[resource];

    return Object.keys(permissions).every((permission) => {
      // @ts-ignore
      return configuration.actions[resource]?.[permission] === true;
    });
  });
};

```

--------------------------------------------------------------------------------
/typescript/src/openai/toolkit.ts:
--------------------------------------------------------------------------------

```typescript
import StripeAPI from '../shared/api';
import tools from '../shared/tools';
import {isToolAllowed, type Configuration} from '../shared/configuration';
import {zodToJsonSchema} from 'zod-to-json-schema';
import type {
  ChatCompletionTool,
  ChatCompletionMessageToolCall,
  ChatCompletionToolMessageParam,
} from 'openai/resources';

class StripeAgentToolkit {
  private _stripe: StripeAPI;

  tools: ChatCompletionTool[];

  constructor({
    secretKey,
    configuration,
  }: {
    secretKey: string;
    configuration: Configuration;
  }) {
    this._stripe = new StripeAPI(secretKey, configuration.context);

    const context = configuration.context || {};
    const filteredTools = tools(context).filter((tool) =>
      isToolAllowed(tool, configuration)
    );

    this.tools = filteredTools.map((tool) => ({
      type: 'function',
      function: {
        name: tool.method,
        description: tool.description,
        parameters: zodToJsonSchema(tool.parameters),
      },
    }));
  }

  getTools(): ChatCompletionTool[] {
    return this.tools;
  }

  /**
   * Processes a single OpenAI tool call by executing the requested function.
   *
   * @param {ChatCompletionMessageToolCall} toolCall - The tool call object from OpenAI containing
   *   function name, arguments, and ID.
   * @returns {Promise<ChatCompletionToolMessageParam>} A promise that resolves to a tool message
   *   object containing the result of the tool execution with the proper format for the OpenAI API.
   */
  async handleToolCall(toolCall: ChatCompletionMessageToolCall) {
    const args = JSON.parse(toolCall.function.arguments);
    const response = await this._stripe.run(toolCall.function.name, args);
    return {
      role: 'tool',
      tool_call_id: toolCall.id,
      content: response,
    } as ChatCompletionToolMessageParam;
  }
}

export default StripeAgentToolkit;

```

--------------------------------------------------------------------------------
/typescript/src/shared/customers/listCustomers.ts:
--------------------------------------------------------------------------------

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listCustomersPrompt = (_context: Context = {}) => `
This tool will fetch a list of Customers from Stripe.

It takes two arguments:
- limit (int, optional): The number of customers to return.
- email (str, optional): A case-sensitive filter on the list based on the customer's email field.
`;

export const listCustomersParameters = (_context: Context = {}) =>
  z.object({
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
      ),
    email: z
      .string()
      .optional()
      .describe(
        "A case-sensitive filter on the list based on the customer's email field. The value must be a string."
      ),
  });

export const listCustomersAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List customers',
});

export const listCustomers = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listCustomersParameters>>
) => {
  try {
    const customers = await stripe.customers.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return customers.data.map((customer) => ({id: customer.id}));
  } catch (error) {
    return 'Failed to list customers';
  }
};

const tool = (context: Context): Tool => ({
  method: 'list_customers',
  name: 'List Customers',
  description: listCustomersPrompt(context),
  parameters: listCustomersParameters(context),
  annotations: listCustomersAnnotations(),
  actions: {
    customers: {
      read: true,
    },
  },
  execute: listCustomers,
});

export default tool;

```

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

```typescript
import {listSubscriptionsParameters} from '@/shared/subscriptions/listSubscriptions';
import {cancelSubscriptionParameters} from '@/shared/subscriptions/cancelSubscription';
import {updateSubscriptionParameters} from '@/shared/subscriptions/updateSubscription';

describe('listSubscriptionsParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listSubscriptionsParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['customer', 'price', 'status', 'limit']);
    expect(fields.length).toBe(4);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listSubscriptionsParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['price', 'status', 'limit']);
    expect(fields.length).toBe(3);
  });
});

describe('cancelSubscriptionParameters', () => {
  it('should return the correct parameters', () => {
    const parameters = cancelSubscriptionParameters({});
    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['subscription']);
  });
});

describe('updateSubscriptionParameters', () => {
  it('should return the correct parameters', () => {
    const parameters = updateSubscriptionParameters({});
    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['subscription', 'proration_behavior', 'items']);
  });

  it('should have the required subscription parameter', () => {
    const parameters = updateSubscriptionParameters({});
    expect(parameters.shape.subscription).toBeDefined();
  });

  it('should have the optional parameters defined', () => {
    const parameters = updateSubscriptionParameters({});
    expect(parameters.shape.proration_behavior).toBeDefined();
    expect(parameters.shape.items).toBeDefined();
  });
});

```

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

```typescript
import {createInvoiceParameters} from '@/shared/invoices/createInvoice';
import {listInvoicesParameters} from '@/shared/invoices/listInvoices';
import {finalizeInvoiceParameters} from '@/shared/invoices/finalizeInvoice';

describe('createInvoiceParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = createInvoiceParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['customer', 'days_until_due']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = createInvoiceParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['days_until_due']);
    expect(fields.length).toBe(1);
  });
});

describe('listInvoicesParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = listInvoicesParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['customer', 'limit']);
    expect(fields.length).toBe(2);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = listInvoicesParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['limit']);
    expect(fields.length).toBe(1);
  });
});

describe('finalizeInvoiceParameters', () => {
  it('should return the correct parameters if no context', () => {
    const parameters = finalizeInvoiceParameters({});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['invoice']);
    expect(fields.length).toBe(1);
  });

  it('should return the correct parameters if customer is specified', () => {
    const parameters = finalizeInvoiceParameters({customer: 'cus_123'});

    const fields = Object.keys(parameters.shape);
    expect(fields).toEqual(['invoice']);
    expect(fields.length).toBe(1);
  });
});

```

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

```typescript
import {listSubscriptionsPrompt} from '@/shared/subscriptions/listSubscriptions';
import {cancelSubscriptionPrompt} from '@/shared/subscriptions/cancelSubscription';
import {updateSubscriptionPrompt} from '@/shared/subscriptions/updateSubscription';

describe('listSubscriptionsPrompt', () => {
  it('should return the correct prompt with no context', () => {
    const prompt = listSubscriptionsPrompt({});

    expect(prompt).toContain('This tool will list all subscriptions in Stripe');
    expect(prompt).toContain('four arguments');
    expect(prompt).toContain('- customer (str, optional)');
    expect(prompt).toContain('- price (str, optional)');
    expect(prompt).toContain('- status (str, optional)');
    expect(prompt).toContain('- limit (int, optional)');
  });

  it('should return the correct prompt with customer in context', () => {
    const prompt = listSubscriptionsPrompt({customer: 'cus_123'});

    expect(prompt).toContain('This tool will list all subscriptions in Stripe');
    expect(prompt).toContain('three arguments');
    expect(prompt).not.toContain('- customer (str, optional)');
    expect(prompt).toContain('- price (str, optional)');
    expect(prompt).toContain('- status (str, optional)');
    expect(prompt).toContain('- limit (int, optional)');
  });
});

describe('cancelSubscriptionPrompt', () => {
  it('should return the correct prompt', () => {
    const prompt = cancelSubscriptionPrompt({});

    expect(prompt).toContain('This tool will cancel a subscription in Stripe');
    expect(prompt).toContain('- subscription (str, required)');
  });
});

describe('updateSubscriptionPrompt', () => {
  it('should return the correct prompt', () => {
    const prompt = updateSubscriptionPrompt({});

    expect(prompt).toContain(
      'This tool will update an existing subscription in Stripe'
    );
    expect(prompt).toContain('- subscription (str, required)');
    expect(prompt).toContain('- proration_behavior (str, optional)');
    expect(prompt).toContain('- items (array, optional)');
  });
});

```

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

```yaml
name: Bug report
description: Create a report to help us improve
labels: ["bug"]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  - type: textarea
    id: what-happened
    attributes:
      label: Describe the bug
      description: A clear and concise description of what the bug is.
      placeholder: Tell us what you see!
    validations:
      required: true
  - type: textarea
    id: repro-steps
    attributes:
      label: To Reproduce
      description: Steps to reproduce the behavior
      placeholder: |
        1. Fetch a '...'
        2. Update the '....'
        3. See error
    validations:
      required: true
  - type: textarea
    id: expected-behavior
    attributes:
      label: Expected behavior
      description: A clear and concise description of what you expected to happen.
    validations:
      required: true
  - type: textarea
    id: code-snippets
    attributes:
      label: Code snippets
      description: If applicable, add code snippets to help explain your problem.
      render: Python
    validations:
      required: false
  - type: input
    id: os
    attributes:
      label: OS
      placeholder: macOS
    validations:
      required: true
  - type: input
    id: language-version
    attributes:
      label: Language version
      placeholder: Python 3.10.4
    validations:
      required: true
  - type: input
    id: lib-version
    attributes:
      label: Library version
      placeholder: stripe-python v2.73.0
    validations:
      required: true
  - type: input
    id: api-version
    attributes:
      label: API version
      description: See [Versioning](https://stripe.com/docs/api/versioning) in the API Reference to find which version you're using
      placeholder: "2020-08-27"
    validations:
      required: true
  - type: textarea
    id: additional-context
    attributes:
      label: Additional context
      description: Add any other context about the problem here.
    validations:
      required: false

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listDisputesPrompt = (_context: Context = {}) => `
This tool will fetch a list of disputes in Stripe.

It takes the following arguments:
- charge (string, optional): Only return disputes associated to the charge specified by this charge ID.
- payment_intent (string, optional): Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.
`;

export const listDisputesParameters = (_context: Context = {}) =>
  z.object({
    charge: z
      .string()
      .optional()
      .describe(
        'Only return disputes associated to the charge specified by this charge ID.'
      ),
    payment_intent: z
      .string()
      .optional()
      .describe(
        'Only return disputes associated to the PaymentIntent specified by this PaymentIntent ID.'
      ),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .default(10)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
      ),
  });

export const listDisputesAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List disputes',
});

export const listDisputes = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listDisputesParameters>>
) => {
  try {
    const disputes = await stripe.disputes.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return disputes.data.map((dispute) => ({id: dispute.id}));
  } catch (error) {
    return 'Failed to list disputes';
  }
};

const tool = (context: Context): Tool => ({
  method: 'list_disputes',
  name: 'List Disputes',
  description: listDisputesPrompt(context),
  parameters: listDisputesParameters(context),
  annotations: listDisputesAnnotations(),
  actions: {
    disputes: {
      read: true,
    },
  },
  execute: listDisputes,
});

export default tool;

```

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

```typescript
import {createInvoiceItem} from '@/shared/invoiceItems/createInvoiceItem';

const Stripe = jest.fn().mockImplementation(() => ({
  invoiceItems: {
    create: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createInvoiceItem', () => {
  it('should create an invoice item and return it', async () => {
    const params = {
      customer: 'cus_123456',
      price: 'price_123456',
      invoice: 'in_123456',
    };

    const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};

    const context = {};

    stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);

    const result = await createInvoiceItem(stripe, context, params);

    expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockInvoiceItem);
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      customer: 'cus_123456',
      price: 'price_123456',
      invoice: 'in_123456',
    };

    const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};

    const context = {
      account: 'acct_123456',
    };

    stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);

    const result = await createInvoiceItem(stripe, context, params);

    expect(stripe.invoiceItems.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockInvoiceItem);
  });

  it('should create an invoice item with a customer if included in context', async () => {
    const params = {
      price: 'price_123456',
      invoice: 'in_123456',
    };

    const mockInvoiceItem = {id: 'ii_123456', invoice: 'in_123456'};

    const context = {
      customer: 'cus_123456',
    };

    stripe.invoiceItems.create.mockResolvedValue(mockInvoiceItem);

    const result = await createInvoiceItem(stripe, context, params);

    expect(stripe.invoiceItems.create).toHaveBeenCalledWith(
      {
        ...params,
        customer: context.customer,
      },
      undefined
    );
    expect(result).toEqual(mockInvoiceItem);
  });
});

```

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

```yaml
name: CI

on:
  workflow_dispatch: {}
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  typescript-build:
    name: Build - TypeScript
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ./typescript

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.11.0

      - name: Node
        uses: actions/setup-node@v4
        with:
          node-version: "18"

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm run build

      - name: Clean
        run: pnpm run clean

      - name: Lint
        run: pnpm run lint

      - name: Prettier
        run: pnpm run prettier-check

      - name: Test
        run: pnpm run test

  modelcontextprotocol-build:
    name: Build - Model Context Protocol
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ./modelcontextprotocol

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.11.0

      - name: Node
        uses: actions/setup-node@v4
        with:
          node-version: "18"

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm run build

      - name: Clean
        run: pnpm run clean

      - name: Lint
        run: pnpm run lint

      - name: Prettier
        run: pnpm run prettier-check

      - name: Test
        run: pnpm run test

  python-build:
    name: Build - Python
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: ./python

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install
        run: make venv

      - name: Build
        run: |
          set -x
          source venv/bin/activate
          rm -rf build dist *.egg-info
          make build
          python -m twine check dist/*

      - name: Test
        run: |
          make venv
          make test

```

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

```python
import asyncio
import os
from pydantic import BaseModel, Field

from dotenv import load_dotenv
load_dotenv()

from agents import Agent, Runner
from agents.tool import FileSearchTool

from stripe_agent_toolkit.openai.toolkit import StripeAgentToolkit

stripe_agent_toolkit = StripeAgentToolkit(
    secret_key=os.getenv("STRIPE_SECRET_KEY"),
    configuration={
        "actions": {
            "customers": {
                "create": True,
            },
            "products": {
                "create": True,
            },
            "prices": {
                "create": True,
            },
            "invoice_items": {
                "create": True,
            },
            "invoices": {
                "create": True,
                "update": True,
            },
        }
    },
)

class InvoiceOutput(BaseModel):
    name: str = Field(description="The name of the customer")
    email: str = Field(description="The email of the customer")
    service: str = Field(description="The service that the customer is invoiced for")
    amount_due: int = Field(description="The dollar amount due for the invoice. Convert text to dollar amounts if needed.")
    id: str = Field(description="The id of the stripe invoice")

class InvoiceListOutput(BaseModel):
    invoices: list[InvoiceOutput]

invoice_agent = Agent(
    name="Invoice Agent",
    instructions="You are an expert at using the Stripe API to create, finalize, and send invoices to customers.",
    tools=stripe_agent_toolkit.get_tools(),
)

file_search_agent = Agent(
    name="File Search Agent",
    instructions="You are an expert at searching for financial documents.",
    tools=[
        FileSearchTool(
            max_num_results=50,
            vector_store_ids=[os.getenv("OPENAI_VECTOR_STORE_ID")],
        )
    ],
    output_type=InvoiceListOutput,
    handoffs=[invoice_agent]
)

async def main():
    assignment = "Search for all customers that haven't paid across all of my documents. Handoff to the invoice agent to create, finalize, and send an invoice for each."

    outstanding_invoices = await Runner.run(
        file_search_agent,
        assignment,
    )

    invoices = outstanding_invoices.final_output

    print(invoices)

if __name__ == "__main__":
    asyncio.run(main())

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listPaymentIntentsPrompt = (context: Context = {}) => {
  const customerArg = context.customer
    ? `The customer is already set in the context: ${context.customer}.`
    : `- customer (str, optional): The ID of the customer to list payment intents for.\n`;

  return `
This tool will list payment intents in Stripe.

It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
${customerArg}
- limit (int, optional): The number of payment intents to return.
`;
};

export const listPaymentIntents = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listPaymentIntentsParameters>>
) => {
  try {
    if (context.customer) {
      params.customer = context.customer;
    }

    const paymentIntents = await stripe.paymentIntents.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return paymentIntents.data;
  } catch (error) {
    return 'Failed to list payment intents';
  }
};

export const listPaymentIntentsAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List payment intents',
});

export const listPaymentIntentsParameters = (
  context: Context = {}
): z.AnyZodObject => {
  const schema = z.object({
    customer: z
      .string()
      .optional()
      .describe('The ID of the customer to list payment intents for.'),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
      ),
  });

  if (context.customer) {
    return schema.omit({customer: true});
  } else {
    return schema;
  }
};

const tool = (context: Context): Tool => ({
  method: 'list_payment_intents',
  name: 'List Payment Intents',
  description: listPaymentIntentsPrompt(context),
  parameters: listPaymentIntentsParameters(context),
  annotations: listPaymentIntentsAnnotations(),
  actions: {
    paymentIntents: {
      read: true,
    },
  },
  execute: listPaymentIntents,
});

export default tool;

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createPaymentLinkPrompt = (_context: Context = {}) => `
This tool will create a payment link in Stripe.

It takes two arguments:
- price (str): The ID of the price to create the payment link for.
- quantity (int): The quantity of the product to include in the payment link.
- redirect_url (str, optional): The URL to redirect to after the payment is completed.
`;

export const createPaymentLink = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createPaymentLinkParameters>>
) => {
  try {
    const paymentLink = await stripe.paymentLinks.create(
      {
        line_items: [{price: params.price, quantity: params.quantity}],
        ...(params.redirect_url
          ? {
              after_completion: {
                type: 'redirect',
                redirect: {
                  url: params.redirect_url,
                },
              },
            }
          : undefined),
      },
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {id: paymentLink.id, url: paymentLink.url};
  } catch (error) {
    return 'Failed to create payment link';
  }
};

export const createPaymentLinkAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create payment link',
});

export const createPaymentLinkParameters = (_context: Context = {}) =>
  z.object({
    price: z
      .string()
      .describe('The ID of the price to create the payment link for.'),
    quantity: z
      .number()
      .int()
      .describe('The quantity of the product to include.'),
    redirect_url: z
      .string()
      .optional()
      .describe('The URL to redirect to after the payment is completed.'),
  });

const tool = (context: Context): Tool => ({
  method: 'create_payment_link',
  name: 'Create Payment Link',
  description: createPaymentLinkPrompt(context),
  parameters: createPaymentLinkParameters(context),
  annotations: createPaymentLinkAnnotations(),
  actions: {
    paymentLinks: {
      create: true,
    },
  },
  execute: createPaymentLink,
});

export default tool;

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createInvoicePrompt = (context: Context = {}) => {
  const customerArg = context.customer
    ? `The customer is already set in the context: ${context.customer}.`
    : `- customer (str): The ID of the customer to create the invoice for.\n`;

  return `
  This tool will create an invoice in Stripe.
  
  It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
  ${customerArg}
  - days_until_due (int, optional): The number of days until the invoice is due.
  `;
};

export const createInvoiceParameters = (
  context: Context = {}
): z.AnyZodObject => {
  const schema = z.object({
    customer: z
      .string()
      .describe('The ID of the customer to create the invoice for.'),
    days_until_due: z
      .number()
      .int()
      .optional()
      .describe('The number of days until the invoice is due.'),
  });

  if (context.customer) {
    return schema.omit({customer: true});
  } else {
    return schema;
  }
};

export const createInvoiceAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create invoice',
});

export const createInvoice = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createInvoiceParameters>>
) => {
  try {
    if (context.customer) {
      params.customer = context.customer;
    }

    const invoice = await stripe.invoices.create(
      {
        ...params,
        collection_method: 'send_invoice',
      },
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {
      id: invoice.id,
      url: invoice.hosted_invoice_url,
      customer: invoice.customer,
      status: invoice.status,
    };
  } catch (error) {
    return 'Failed to create invoice';
  }
};

const tool = (context: Context): Tool => ({
  method: 'create_invoice',
  name: 'Create Invoice',
  description: createInvoicePrompt(context),
  parameters: createInvoiceParameters(context),
  annotations: createInvoiceAnnotations(),
  actions: {
    invoices: {
      create: true,
    },
  },
  execute: createInvoice,
});

export default tool;

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const createInvoiceItem = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof createInvoiceItemParameters>>
) => {
  try {
    if (context.customer) {
      params.customer = context.customer;
    }

    const invoiceItem = await stripe.invoiceItems.create(
      // @ts-ignore
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return {
      id: invoiceItem.id,
      invoice: invoiceItem.invoice,
    };
  } catch (error) {
    return 'Failed to create invoice item';
  }
};

export const createInvoiceItemAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: false,
  openWorldHint: true,
  readOnlyHint: false,
  title: 'Create invoice item',
});

export const createInvoiceItemParameters = (
  context: Context = {}
): z.AnyZodObject => {
  const schema = z.object({
    customer: z
      .string()
      .describe('The ID of the customer to create the invoice item for.'),
    price: z.string().describe('The ID of the price for the item.'),
    invoice: z
      .string()
      .describe('The ID of the invoice to create the item for.'),
  });

  if (context.customer) {
    return schema.omit({customer: true});
  } else {
    return schema;
  }
};

export const createInvoiceItemPrompt = (context: Context = {}) => {
  const customerArg = context.customer
    ? `The customer is already set in the context: ${context.customer}.`
    : `- customer (str): The ID of the customer to create the invoice item for.\n`;

  return `
This tool will create an invoice item in Stripe.

It takes ${context.customer ? 'two' : 'three'} arguments'}:
${customerArg}
- price (str): The ID of the price to create the invoice item for.
- invoice (str): The ID of the invoice to create the invoice item for.
`;
};

const tool = (context: Context): Tool => ({
  method: 'create_invoice_item',
  name: 'Create Invoice Item',
  description: createInvoiceItemPrompt(context),
  parameters: createInvoiceItemParameters(context),
  annotations: createInvoiceItemAnnotations(),
  actions: {
    invoiceItems: {
      create: true,
    },
  },
  execute: createInvoiceItem,
});

export default tool;

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listInvoices = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listInvoicesParameters>>
) => {
  try {
    if (context.customer) {
      params.customer = context.customer;
    }

    const invoices = await stripe.invoices.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return invoices.data;
  } catch (error) {
    return 'Failed to list invoices';
  }
};

export const listInvoicesAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List invoices',
});

export const listInvoicesParameters = (
  context: Context = {}
): z.AnyZodObject => {
  const schema = z.object({
    customer: z
      .string()
      .optional()
      .describe('The ID of the customer to list invoices for.'),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 10.'
      ),
  });

  if (context.customer) {
    return schema.omit({customer: true});
  } else {
    return schema;
  }
};

export const listInvoicesPrompt = (context: Context = {}) => {
  const customerArg = context.customer
    ? `The customer is already set in the context: ${context.customer}.`
    : `- customer (str, optional): The ID of the customer to list invoices for.\n`;

  return `
This tool will fetch a list of Invoices from Stripe.

It takes ${context.customer ? 'one' : 'two'} argument${context.customer ? '' : 's'}:
${customerArg}
- limit (int, optional): The number of invoices to return.
`;
};

export const finalizeInvoicePrompt = (_context: Context = {}) => `
This tool will finalize an invoice in Stripe.

It takes one argument:
- invoice (str): The ID of the invoice to finalize.
`;

const tool = (context: Context): Tool => ({
  method: 'list_invoices',
  name: 'List Invoices',
  description: listInvoicesPrompt(context),
  parameters: listInvoicesParameters(context),
  annotations: listInvoicesAnnotations(),
  actions: {
    invoices: {
      read: true,
    },
  },
  execute: listInvoices,
});

export default tool;

```

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

```typescript
import {createProduct} from '@/shared/products/createProduct';
import {listProducts} from '@/shared/products/listProducts';

const Stripe = jest.fn().mockImplementation(() => ({
  products: {
    create: jest.fn(),
    list: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createProduct', () => {
  it('should create a product and return it', async () => {
    const params = {
      name: 'Test Product',
    };

    const context = {};

    const mockProduct = {id: 'prod_123456', name: 'Test Product'};
    stripe.products.create.mockResolvedValue(mockProduct);

    const result = await createProduct(stripe, context, params);

    expect(stripe.products.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockProduct);
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      name: 'Test Product',
    };

    const context = {
      account: 'acct_123456',
    };

    const mockProduct = {id: 'prod_123456', name: 'Test Product'};
    stripe.products.create.mockResolvedValue(mockProduct);

    const result = await createProduct(stripe, context, params);

    expect(stripe.products.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockProduct);
  });
});

describe('listProducts', () => {
  it('should list products and return them', async () => {
    const mockProducts = [
      {id: 'prod_123456', name: 'Test Product 1'},
      {id: 'prod_789012', name: 'Test Product 2'},
    ];

    const context = {};

    stripe.products.list.mockResolvedValue({data: mockProducts});
    const result = await listProducts(stripe, context, {});

    expect(stripe.products.list).toHaveBeenCalledWith({}, undefined);
    expect(result).toEqual(mockProducts);
  });

  it('should specify the connected account if included in context', async () => {
    const mockProducts = [
      {id: 'prod_123456', name: 'Test Product 1'},
      {id: 'prod_789012', name: 'Test Product 2'},
    ];

    const context = {
      account: 'acct_123456',
    };

    stripe.products.list.mockResolvedValue({data: mockProducts});
    const result = await listProducts(stripe, context, {});

    expect(stripe.products.list).toHaveBeenCalledWith(
      {},
      {
        stripeAccount: context.account,
      }
    );
    expect(result).toEqual(mockProducts);
  });
});

```

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

```typescript
import {createPaymentLink} from '@/shared/paymentLinks/createPaymentLink';

const Stripe = jest.fn().mockImplementation(() => ({
  paymentLinks: {
    create: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createPaymentLink', () => {
  it('should create a payment link and return it', async () => {
    const params = {
      line_items: [
        {
          price: 'price_123456',
          quantity: 1,
        },
      ],
    };

    const mockPaymentLink = {
      id: 'pl_123456',
      url: 'https://example.com',
    };

    const context = {};

    stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);

    const result = await createPaymentLink(stripe, context, {
      price: 'price_123456',
      quantity: 1,
    });

    expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockPaymentLink);
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      line_items: [
        {
          price: 'price_123456',
          quantity: 1,
        },
      ],
    };

    const mockPaymentLink = {
      id: 'pl_123456',
      url: 'https://example.com',
    };

    const context = {
      account: 'acct_123456',
    };

    stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);

    const result = await createPaymentLink(stripe, context, {
      price: 'price_123456',
      quantity: 1,
    });

    expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockPaymentLink);
  });

  it('should specify the redirect URL if included in params', async () => {
    const params = {
      line_items: [
        {
          price: 'price_123456',
          quantity: 1,
        },
      ],
      after_completion: {
        type: 'redirect',
        redirect: {
          url: 'https://example.com',
        },
      },
    };

    const mockPaymentLink = {
      id: 'pl_123456',
      url: 'https://example.com',
    };

    const context = {};

    stripe.paymentLinks.create.mockResolvedValue(mockPaymentLink);

    const result = await createPaymentLink(stripe, context, {
      price: 'price_123456',
      quantity: 1,
      redirect_url: 'https://example.com',
    });

    expect(stripe.paymentLinks.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockPaymentLink);
  });
});

```

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

```typescript
import {searchDocumentation} from '@/shared/documentation/searchDocumentation';
import {z} from 'zod';
import {searchDocumentationParameters} from '@/shared/documentation/searchDocumentation';

const Stripe = jest.fn().mockImplementation(() => ({}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

const EXPECTED_HEADERS = {
  'Content-Type': 'application/json',
  'X-Requested-With': 'fetch',
  'User-Agent': 'stripe-agent-toolkit-typescript',
};

describe('searchDocumentation', () => {
  it('should search for Stripe documentation and return sources', async () => {
    const question = 'How to create Stripe checkout session?';
    const requestBody: z.infer<
      ReturnType<typeof searchDocumentationParameters>
    > = {
      question: question,
      language: 'ruby',
    };

    const sources = [
      {
        type: 'docs',
        url: 'https://docs.stripe.com/payments/checkout/how-checkout-works',
        title: 'How checkout works',
        content: '...',
      },
    ];
    const mockResponse = {
      question: question,
      status: 'success',
      sources: sources,
    };

    const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
      ok: true,
      status: 200,
      json: jest.fn().mockResolvedValueOnce(mockResponse),
    } as unknown as Response);

    const result = await searchDocumentation(stripe, {}, requestBody);

    expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
      method: 'POST',
      headers: EXPECTED_HEADERS,
      body: JSON.stringify(requestBody),
    });

    expect(result).toEqual(sources);
  });

  it('should return failure string if search failed', async () => {
    const question = 'What is the meaning of life?';
    const requestBody = {
      question: question,
    };

    const mockError = {
      error: 'Invalid query',
      message:
        'Unable to process your question. Please rephrase it to be more specific.',
    };

    const fetchMock = jest.spyOn(global, 'fetch').mockResolvedValueOnce({
      ok: false,
      status: 400,
      json: jest.fn().mockResolvedValueOnce(mockError),
    } as unknown as Response);

    const result = await searchDocumentation(stripe, {}, requestBody);

    expect(fetchMock).toHaveBeenCalledWith('https://ai.stripe.com/search', {
      method: 'POST',
      headers: EXPECTED_HEADERS,
      body: JSON.stringify(requestBody),
    });

    expect(result).toEqual('Failed to search documentation');
  });
});

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';
export const searchDocumentationPrompt = (_context: Context = {}) => `
This tool will take in a user question about integrating with Stripe in their application, then search and retrieve relevant Stripe documentation to answer the question.

It takes two arguments:
- question (str): The user question to search an answer for in the Stripe documentation.
- language (str, optional): The programming language to search for in the the documentation.
`;

export const searchDocumentationParameters = (
  _context: Context = {}
): z.AnyZodObject =>
  z.object({
    question: z
      .string()
      .describe(
        'The user question about integrating with Stripe will be used to search the documentation.'
      ),
    language: z
      .enum(['dotnet', 'go', 'java', 'node', 'php', 'ruby', 'python', 'curl'])
      .optional()
      .describe(
        'The programming language to search for in the the documentation.'
      ),
  });

export const searchDocumentationAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'Search Stripe documentation',
});

export const searchDocumentation = async (
  _stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof searchDocumentationParameters>>
) => {
  try {
    const endpoint = 'https://ai.stripe.com/search';
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'fetch',
        'User-Agent':
          context.mode === 'modelcontextprotocol'
            ? 'stripe-mcp'
            : 'stripe-agent-toolkit-typescript',
      },
      body: JSON.stringify(params),
    });

    // If status not in 200-299 range, throw error
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    return data?.sources;
  } catch (error) {
    return 'Failed to search documentation';
  }
};

const tool = (context: Context): Tool => ({
  method: 'search_stripe_documentation',
  name: 'Search Stripe Documentation',
  description: searchDocumentationPrompt(context),
  parameters: searchDocumentationParameters(context),
  annotations: searchDocumentationAnnotations(),
  actions: {
    documentation: {
      read: true,
    },
  },
  execute: searchDocumentation,
});

export default tool;

```

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

```json
{
  "name": "@stripe/agent-toolkit",
  "version": "0.7.11",
  "homepage": "https://github.com/stripe/agent-toolkit",
  "scripts": {
    "build": "tsup",
    "clean": "rm -rf langchain ai-sdk modelcontextprotocol openai cloudflare",
    "lint": "eslint \"./**/*.ts*\"",
    "prettier": "prettier './**/*.{js,ts,md,html,css}' --write",
    "prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check",
    "test": "jest",
    "eval": "cd ../evals && pnpm test"
  },
  "exports": {
    "./langchain": {
      "types": "./langchain/index.d.ts",
      "require": "./langchain/index.js",
      "import": "./langchain/index.mjs"
    },
    "./ai-sdk": {
      "types": "./ai-sdk/index.d.ts",
      "require": "./ai-sdk/index.js",
      "import": "./ai-sdk/index.mjs"
    },
    "./openai": {
      "types": "./openai/index.d.ts",
      "require": "./openai/index.js",
      "import": "./openai/index.mjs"
    },
    "./modelcontextprotocol": {
      "types": "./modelcontextprotocol/index.d.ts",
      "require": "./modelcontextprotocol/index.js",
      "import": "./modelcontextprotocol/index.mjs"
    },
    "./cloudflare": {
      "types": "./cloudflare/index.d.ts",
      "require": "./cloudflare/index.js",
      "import": "./cloudflare/index.mjs"
    }
  },
  "packageManager": "[email protected]",
  "engines": {
    "node": ">=18"
  },
  "author": "Stripe <[email protected]> (https://stripe.com/)",
  "contributors": [
    "Steve Kaliski <[email protected]>"
  ],
  "license": "MIT",
  "devDependencies": {
    "@eslint/compat": "^1.2.4",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.10.5",
    "@typescript-eslint/eslint-plugin": "^8.19.1",
    "eslint": "^9.17.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-import": "^2.31.0",
    "eslint-plugin-jest": "^28.10.0",
    "eslint-plugin-prettier": "^5.2.1",
    "globals": "^15.14.0",
    "jest": "^29.7.0",
    "prettier": "^3.4.2",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "tsup": "^8.3.5",
    "typescript": "^5.8.3"
  },
  "dependencies": {
    "stripe": "^17.5.0",
    "zod": "^3.24.1",
    "zod-to-json-schema": "^3.24.3"
  },
  "peerDependencies": {
    "openai": "^4.86.1",
    "@langchain/core": "^0.3.6",
    "@modelcontextprotocol/sdk": "^1.17.1",
    "ai": "^3.4.7 || ^4.0.0",
    "agents": "^0.0.84"
  },
  "workspaces": [
    ".",
    "examples/*"
  ],
  "files": [
    "ai-sdk/**/*",
    "langchain/**/*",
    "modelcontextprotocol/**/*",
    "openai/**/*",
    "cloudflare/**/*",
    "LICENSE",
    "README.md",
    "VERSION",
    "package.json"
  ]
}

```

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

```typescript
import {createPrice} from '@/shared/prices/createPrice';
import {listPrices} from '@/shared/prices/listPrices';

const Stripe = jest.fn().mockImplementation(() => ({
  prices: {
    create: jest.fn(),
    list: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createPrice', () => {
  it('should create a price and return it', async () => {
    const params = {
      unit_amount: 1000,
      currency: 'usd',
      product: 'prod_123456',
    };

    const context = {};

    const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
    stripe.prices.create.mockResolvedValue(mockPrice);

    const result = await createPrice(stripe, context, params);

    expect(stripe.prices.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual(mockPrice);
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      unit_amount: 1000,
      currency: 'usd',
      product: 'prod_123456',
    };

    const context = {
      account: 'acct_123456',
    };

    const mockPrice = {id: 'price_123456', unit_amount: 1000, currency: 'usd'};
    stripe.prices.create.mockResolvedValue(mockPrice);

    const result = await createPrice(stripe, context, params);

    expect(stripe.prices.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual(mockPrice);
  });
});

describe('listPrices', () => {
  it('should list prices and return them', async () => {
    const mockPrices = [
      {id: 'price_123456', unit_amount: 1000, currency: 'usd'},
      {id: 'price_789012', unit_amount: 2000, currency: 'usd'},
    ];

    const context = {};

    stripe.prices.list.mockResolvedValue({data: mockPrices});
    const result = await listPrices(stripe, context, {});

    expect(stripe.prices.list).toHaveBeenCalledWith({}, undefined);
    expect(result).toEqual(mockPrices);
  });

  it('should specify the connected account if included in context', async () => {
    const mockPrices = [
      {id: 'price_123456', unit_amount: 1000, currency: 'usd'},
      {id: 'price_789012', unit_amount: 2000, currency: 'usd'},
    ];

    const context = {
      account: 'acct_123456',
    };

    stripe.prices.list.mockResolvedValue({data: mockPrices});
    const result = await listPrices(stripe, context, {});

    expect(stripe.prices.list).toHaveBeenCalledWith(
      {},
      {
        stripeAccount: context.account,
      }
    );
    expect(result).toEqual(mockPrices);
  });
});

```

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

```typescript
import {createCustomer} from '@/shared/customers/createCustomer';
import {listCustomers} from '@/shared/customers/listCustomers';

const Stripe = jest.fn().mockImplementation(() => ({
  customers: {
    create: jest.fn(),
    list: jest.fn(),
  },
}));

let stripe: ReturnType<typeof Stripe>;

beforeEach(() => {
  stripe = new Stripe('fake-api-key');
});

describe('createCustomer', () => {
  it('should create a customer and return the id', async () => {
    const params = {
      email: '[email protected]',
      name: 'Test User',
    };

    const context = {};

    const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
    stripe.customers.create.mockResolvedValue(mockCustomer);

    const result = await createCustomer(stripe, context, params);

    expect(stripe.customers.create).toHaveBeenCalledWith(params, undefined);
    expect(result).toEqual({id: mockCustomer.id});
  });

  it('should specify the connected account if included in context', async () => {
    const params = {
      email: '[email protected]',
      name: 'Test User',
    };

    const context = {
      account: 'acct_123456',
    };

    const mockCustomer = {id: 'cus_123456', email: '[email protected]'};
    stripe.customers.create.mockResolvedValue(mockCustomer);

    const result = await createCustomer(stripe, context, params);

    expect(stripe.customers.create).toHaveBeenCalledWith(params, {
      stripeAccount: context.account,
    });
    expect(result).toEqual({id: mockCustomer.id});
  });
});

describe('listCustomers', () => {
  it('should list customers and return their ids', async () => {
    const mockCustomers = [
      {id: 'cus_123456', email: '[email protected]'},
      {id: 'cus_789012', email: '[email protected]'},
    ];

    const context = {};

    stripe.customers.list.mockResolvedValue({data: mockCustomers});
    const result = await listCustomers(stripe, context, {});

    expect(stripe.customers.list).toHaveBeenCalledWith({}, undefined);
    expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
  });

  it('should specify the connected account if included in context', async () => {
    const mockCustomers = [
      {id: 'cus_123456', email: '[email protected]'},
      {id: 'cus_789012', email: '[email protected]'},
    ];

    const context = {
      account: 'acct_123456',
    };

    stripe.customers.list.mockResolvedValue({data: mockCustomers});
    const result = await listCustomers(stripe, context, {});

    expect(stripe.customers.list).toHaveBeenCalledWith(
      {},
      {
        stripeAccount: context.account,
      }
    );
    expect(result).toEqual(mockCustomers.map(({id}) => ({id})));
  });
});

```

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

```python
import env
import asyncio
from emailer import Emailer, Email
from typing import Union, List
import support_agent
import markdown as markdown
import json

from agents import (
    ItemHelpers,
    TResponseInputItem,
)


env.ensure("STRIPE_SECRET_KEY")
env.ensure("OPENAI_API_KEY")

email_address = env.ensure("EMAIL_ADDRESS")
support_address = env.get_or("SUPPORT_ADDRESS", email_address)
email_password = env.ensure("EMAIL_PASSWORD")
emailer = Emailer(email_address, email_password, support_address)


def unsure(str: str) -> bool:
    return (
        "not sure" in str
        or "unsure" in str
        or "don't know" in str
        or "dont know" in str
        or "do not know" in str
    )


async def respond(thread: List[Email]) -> Union[Email, None]:
    most_recent = thread[-1]
    print(f"Got unread email:\n  {json.dumps(most_recent.to_dict())}")

    # Loop through the entire thread to add historical context for the agent
    input_items: list[TResponseInputItem] = []
    for email in thread:
        input_items.append(
            {
                "content": (
                    "This is an earlier email:"
                    f"Email from: {email.from_address}\n"
                    f"To: {email.to_address}\n"
                    f"Subject: {email.subject}\n\n"
                    f"{email.body}"
                ),
                "role": "user",
            }
        )

    input_items.append(
        {
            "content": (
                "This the latest email"
                "You can use context from earlier emails"
                "but reply specifically to the following email:"
                f"Email from: {most_recent.from_address}\n"
                f"To: {most_recent.to_address}\n"
                f"Subject: {most_recent.subject}\n\n"
                f"{most_recent.body}"
            ),
            "role": "user",
        }
    )

    print(f"Sending to agent:\n  {json.dumps(input_items)}")

    output = await support_agent.run(input_items)
    body_md = ItemHelpers.text_message_outputs(output.new_items)

    # Handle answers that the agent doesn't know
    if unsure(body_md.lower()):
        print(
            f"Agent doesn't know, ignore response and keep email in the inbox:\n{body_md}"
        )
        return None

    # OpenAI often returns the body in html fences, trim those
    body_html = markdown.markdown(body_md, extensions=["tables"])

    return Email(
        from_address=most_recent.to_address,
        to_address=most_recent.from_address,
        subject=most_recent.subject,
        body=body_html,
    )


async def main():
    await emailer.run(respond, delay=30, mark_read=True)


if __name__ == "__main__":
    asyncio.run(main())

```

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

```typescript
import z from 'zod';
import {isToolAllowed} from '@/shared/configuration';

describe('isToolAllowed', () => {
  it('should return true if all permissions are allowed', () => {
    const tool = {
      method: 'test',
      name: 'Test',
      description: 'Test',
      parameters: z.object({
        foo: z.string(),
      }),
      annotations: {
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
        readOnlyHint: false,
        title: 'Test',
      },
      execute: async (a: any, b: any, c: any) => {},
      actions: {
        customers: {
          create: true,
          read: true,
        },
        invoices: {
          create: true,
          read: true,
        },
      },
    };

    const configuration = {
      actions: {
        customers: {
          create: true,
          read: true,
        },
        invoices: {
          create: true,
          read: true,
        },
      },
    };

    expect(isToolAllowed(tool, configuration)).toBe(true);
  });

  it('should return false if any permission is denied', () => {
    const tool = {
      method: 'test',
      name: 'Test',
      description: 'Test',
      parameters: z.object({
        foo: z.string(),
      }),
      annotations: {
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
        readOnlyHint: false,
        title: 'Test',
      },
      execute: async (a: any, b: any, c: any) => {},
      actions: {
        customers: {
          create: true,
          read: true,
        },
        invoices: {
          create: true,
          read: true,
        },
      },
    };

    const configuration = {
      actions: {
        customers: {
          create: true,
          read: true,
        },
        invoices: {
          create: true,
          read: false,
        },
      },
    };

    expect(isToolAllowed(tool, configuration)).toBe(false);
  });

  it('should return false if any resource is not allowed', () => {
    const tool = {
      method: 'test',
      name: 'Test',
      description: 'Test',
      parameters: z.object({
        foo: z.string(),
      }),
      annotations: {
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
        readOnlyHint: false,
        title: 'Test',
      },
      execute: async (a: any, b: any, c: any) => {},
      actions: {
        paymentLinks: {
          create: true,
        },
      },
    };

    const configuration = {
      actions: {
        customers: {
          create: true,
          read: true,
        },
        invoices: {
          create: true,
          read: true,
        },
      },
    };

    expect(isToolAllowed(tool, configuration)).toBe(false);
  });
});

```

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

```typescript
import Stripe from 'stripe';
import {z} from 'zod';
import type {Context} from '@/shared/configuration';
import type {Tool} from '@/shared/tools';

export const listSubscriptions = async (
  stripe: Stripe,
  context: Context,
  params: z.infer<ReturnType<typeof listSubscriptionsParameters>>
) => {
  try {
    if (context.customer) {
      params.customer = context.customer;
    }

    const subscriptions = await stripe.subscriptions.list(
      params,
      context.account ? {stripeAccount: context.account} : undefined
    );

    return subscriptions.data;
  } catch (error) {
    return 'Failed to list subscriptions';
  }
};

export const listSubscriptionsParameters = (
  context: Context = {}
): z.AnyZodObject => {
  const schema = z.object({
    customer: z
      .string()
      .optional()
      .describe('The ID of the customer to list subscriptions for.'),
    price: z
      .string()
      .optional()
      .describe('The ID of the price to list subscriptions for.'),
    status: z
      .enum([
        'active',
        'past_due',
        'unpaid',
        'canceled',
        'incomplete',
        'incomplete_expired',
        'trialing',
        'all',
      ])
      .optional()
      .describe('The status of the subscriptions to retrieve.'),
    limit: z
      .number()
      .int()
      .min(1)
      .max(100)
      .optional()
      .describe(
        'A limit on the number of objects to be returned. Limit can range between 1 and 100.'
      ),
  });

  if (context.customer) {
    return schema.omit({customer: true});
  } else {
    return schema;
  }
};

export const listSubscriptionsPrompt = (context: Context = {}): string => {
  const customerArg = context.customer
    ? `The customer is already set in the context: ${context.customer}.`
    : `- customer (str, optional): The ID of the customer to list subscriptions for.\n`;

  return `
This tool will list all subscriptions in Stripe.

It takes ${context.customer ? 'three' : 'four'} arguments:
${customerArg}
- price (str, optional): The ID of the price to list subscriptions for.
- status (str, optional): The status of the subscriptions to list.
- limit (int, optional): The number of subscriptions to return.
`;
};

export const listSubscriptionsAnnotations = () => ({
  destructiveHint: false,
  idempotentHint: true,
  openWorldHint: true,
  readOnlyHint: true,
  title: 'List subscriptions',
});

const tool = (context: Context): Tool => ({
  method: 'list_subscriptions',
  name: 'List Subscriptions',
  description: listSubscriptionsPrompt(context),
  parameters: listSubscriptionsParameters(context),
  annotations: listSubscriptionsAnnotations(),
  actions: {
    subscriptions: {
      read: true,
    },
  },
  execute: listSubscriptions,
});

export default tool;

```

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

```typescript
import {z} from 'zod';

import createCustomerTool from '@/shared/customers/createCustomer';
import listCustomersTool from '@/shared/customers/listCustomers';
import createProductTool from '@/shared/products/createProduct';
import listProductsTool from '@/shared/products/listProducts';
import createPriceTool from '@/shared/prices/createPrice';
import listPricesTool from '@/shared/prices/listPrices';
import createPaymentLinkTool from '@/shared/paymentLinks/createPaymentLink';
import createInvoiceTool from '@/shared/invoices/createInvoice';
import listInvoicesTool from '@/shared/invoices/listInvoices';
import createInvoiceItemTool from '@/shared/invoiceItems/createInvoiceItem';
import finalizeInvoiceTool from '@/shared/invoices/finalizeInvoice';
import retrieveBalanceTool from '@/shared/balance/retrieveBalance';
import listCouponsTool from '@/shared/coupons/listCoupons';
import createCouponTool from '@/shared/coupons/createCoupon';
import createRefundTool from '@/shared/refunds/createRefund';
import listPaymentIntentsTool from '@/shared/paymentIntents/listPaymentIntents';
import listSubscriptionsTool from '@/shared/subscriptions/listSubscriptions';
import cancelSubscriptionTool from '@/shared/subscriptions/cancelSubscription';
import updateSubscriptionTool from '@/shared/subscriptions/updateSubscription';
import searchDocumentationTool from '@/shared/documentation/searchDocumentation';
import listDisputesTool from '@/shared/disputes/listDisputes';
import updateDisputeTool from '@/shared/disputes/updateDispute';

import {Context} from './configuration';
import Stripe from 'stripe';

export type Tool = {
  method: string;
  name: string;
  description: string;
  parameters: z.ZodObject<any, any, any, any>;
  annotations: {
    destructiveHint?: boolean;
    idempotentHint?: boolean;
    openWorldHint?: boolean;
    readOnlyHint?: boolean;
    title?: string;
  };
  actions: {
    [key: string]: {
      [action: string]: boolean;
    };
  };
  execute: (stripe: Stripe, context: Context, params: any) => Promise<any>;
};

const tools = (context: Context): Tool[] => [
  createCustomerTool(context),
  listCustomersTool(context),
  createProductTool(context),
  listProductsTool(context),
  createPriceTool(context),
  listPricesTool(context),
  createPaymentLinkTool(context),
  createInvoiceTool(context),
  listInvoicesTool(context),
  createInvoiceItemTool(context),
  finalizeInvoiceTool(context),
  retrieveBalanceTool(context),
  createRefundTool(context),
  listPaymentIntentsTool(context),
  listSubscriptionsTool(context),
  cancelSubscriptionTool(context),
  updateSubscriptionTool(context),
  searchDocumentationTool(context),
  listCouponsTool(context),
  createCouponTool(context),
  updateDisputeTool(context),
  listDisputesTool(context),
];

export default tools;

```
Page 1/3FirstPrevNextLast