This is page 1 of 3. Use http://codebase.md/stripe/agent-toolkit?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; ```