├── .env.sample ├── .github └── CODEOWNERS ├── .gitignore ├── LICENSE ├── PROMPTS.md ├── README.md ├── modelcontextprotocol ├── .gitignore ├── .prettierrc ├── README.md ├── eslint.config.mjs ├── jest.config.ts ├── package.json ├── scripts │ └── publish ├── src │ ├── index.ts │ ├── stdio-transport.ts │ └── types │ │ └── stdio.d.ts └── tsconfig.json ├── python ├── CHANGELOG.md ├── README.md ├── examples │ ├── crewai │ │ ├── .env.sample │ │ ├── app_agent.py │ │ └── requirements.txt │ ├── langchain │ │ ├── .env.sample │ │ ├── app_agent.py │ │ └── requirements.txt │ ├── openai │ │ ├── .env.sample │ │ ├── app_agent.py │ │ ├── app_assistant_chatbot.py │ │ └── requirements.txt │ └── readme.md ├── paypal_agent_toolkit │ ├── __init__.py │ ├── crewai │ │ ├── tool.py │ │ └── toolkit.py │ ├── langchain │ │ ├── tool.py │ │ └── toolkit.py │ ├── openai │ │ ├── __init__.py │ │ ├── tool.py │ │ └── toolkit.py │ └── shared │ │ ├── __init__.py │ │ ├── api.py │ │ ├── configuration.py │ │ ├── constants.py │ │ ├── disputes │ │ ├── parameters.py │ │ ├── prompts.py │ │ └── tool_handlers.py │ │ ├── invoices │ │ ├── parameters.py │ │ ├── prompts.py │ │ └── tool_handlers.py │ │ ├── logger_util.py │ │ ├── orders │ │ ├── __init__.py │ │ ├── parameters.py │ │ ├── payload_util.py │ │ ├── prompts.py │ │ └── tool_handlers.py │ │ ├── paypal_client.py │ │ ├── subscriptions │ │ ├── __init__.py │ │ ├── parameters.py │ │ ├── prompts.py │ │ └── tool_handlers.py │ │ ├── telemetry.py │ │ ├── tools.py │ │ ├── tracking │ │ ├── parameters.py │ │ ├── prompts.py │ │ └── tool_handlers.py │ │ └── transactions │ │ ├── parameters.py │ │ ├── prompt.py │ │ └── tool_handlers.py ├── pyproject.toml └── requirements.txt └── typescript ├── .env.sample ├── README.md ├── examples └── ai-sdk │ ├── README.md │ ├── index.ts │ └── package.json ├── jest.config.ts ├── package.json ├── src ├── ai-sdk │ ├── index.ts │ ├── toolkit.ts │ ├── tools.ts │ └── workflows.ts ├── index.ts ├── modelcontextprotocol │ ├── index.ts │ ├── mcpToolkit.ts │ └── toolkit.ts └── shared │ ├── api.ts │ ├── client.ts │ ├── configuration.ts │ ├── functions.ts │ ├── parameters.ts │ ├── payloadUtils.ts │ ├── prompts.ts │ └── tools.ts ├── tsconfig.json └── tsup.config.ts /.env.sample: -------------------------------------------------------------------------------- 1 | ;; 2 | ; Rename this file to .env and add the correct values for the credentials. 3 | ;; 4 | ; The configurations required for the LLM. Please refer your LLM model instructions 5 | ; The examples use the OpenAI Azure LLM internal to PayPal. 6 | OPENAI_API_BASE= 7 | AZURE_OAUTH_URL= 8 | AZURE_CLIENT_ID= 9 | AZURE_SECRET= 10 | AZURE_OAUTH_SCOPES= 11 | 12 | ; The PayPal Client ID and Client Secret in your PayPal Developer Dashboard. These credentials are necessary to authenticate API calls and integrate PayPal services. 13 | PAYPAL_CLIENT_ID= 14 | PAYPAL_CLIENT_SECRET= 15 | 16 | ; Twilio secrets 17 | TWILIO_ACCOUNT_SID= 18 | TWILIO_AUTH_TOKEN= 19 | TWILIO_FROM_NUMBER= 20 | TWILIO_TO_NUMBER= 21 | 22 | ; The PayPal environment to use. Accepted values: 'Sandbox' or 'Production' 23 | PAYPAL_ENVIRONMENT=Sandbox 24 | PAYPAL_LOG_REQUESTS=true 25 | PAYPAL_LOG_RESPONSES=true 26 | 27 | ; AWS Keys 28 | AWS_ACCESS_KEY_ID= 29 | AWS_SECRET_KEY= 30 | AWS_SENDER_EMAIL= 31 | AWS_RECIVER_EMAIL= 32 | AWS_REGION= 33 | AWS_BEDROCK_MODEL_ARN= 34 | 35 | ; Cert issues, set to 0 to ignore 36 | NODE_TLS_REJECT_UNAUTHORIZED= -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Currently for all files on the branch 2 | * @Core-SDK/agent-toolkit-sync 3 | 4 | # Specifically for the github folder 5 | /.github/ @Core-SDK/agent-toolkit-sync 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | dist 3 | coverage 4 | node_modules 5 | package-lock.json 6 | logs.txt 7 | .env 8 | .env.local 9 | typescript/ai-sdk/ 10 | typescript/mcp/ 11 | 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # Virtual environment 19 | .env/ 20 | .venv/ 21 | env/ 22 | venv/ 23 | 24 | # Jupyter Notebook checkpoints 25 | .ipynb_checkpoints 26 | 27 | # Distribution / packaging 28 | build/ 29 | dist/ 30 | *.egg-info/ 31 | .eggs/ 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .nox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Pytest 50 | .pytest_cache/ 51 | 52 | # MyPy 53 | .mypy_cache/ 54 | .dmypy.json 55 | dmypy.json 56 | 57 | # Pyre 58 | .pyre/ 59 | 60 | # Pytype 61 | .pytype/ 62 | 63 | # Cython debug symbols 64 | cython_debug/ 65 | 66 | # VS Code 67 | .vscode/ 68 | 69 | # Mac/Linux/Windows system files 70 | .DS_Store 71 | Thumbs.db 72 | desktop.ini 73 | 74 | # Environments or Secrets 75 | .envrc 76 | *.env 77 | *.secret 78 | *.key 79 | *.pem 80 | 81 | # logs 82 | logs/ 83 | *.log 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2025 PayPal 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | https://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /PROMPTS.md: -------------------------------------------------------------------------------- 1 | # Prompts 2 | 3 | ## Available tools 4 | 5 | The PayPal Agent toolkit provides the following tools: 6 | 7 | ### **Invoices** 8 | 9 | --- 10 | 11 | **`create_invoice`** - Creates a new invoice in the PayPal system. 12 | 13 | - `recipient_email` (string): Email address of the invoice recipient. 14 | - `items` (array): List of items or services to include in the invoice. Each item should have. 15 | - `name` (string): Name of the item. 16 | - `quantity` (number): Quantity of the item. 17 | - `unit_price` (number): Price per unit of the item. 18 | 19 | > **Example Prompt**: 20 | > Create an invoice for {customer_email} including 2 hours of consulting at $150 per hour 21 | 22 | --- 23 | 24 | **`list_invoices`** - Lists invoices with optional pagination and filtering. 25 | 26 | - `page` (number, optional): Page number for pagination. 27 | - `page_size` (number, optional): Number of invoices per page. 28 | - `status` (string, optional): Filter invoices by status (e.g., SENT, PAID). 29 | 30 | > **Example Prompt**: 31 | > List all {status} invoices on page {page} with {page_size} invoices per page 32 | 33 | --- 34 | 35 | **`get_invoice`** - Retrieves details of a specific invoice. 36 | 37 | - `invoice_id` (string): The unique identifier of the invoice. 38 | 39 | > **Example Prompt**: 40 | > What are the details of invoice {invoice_id}? 41 | 42 | --- 43 | 44 | **`send_invoice`** - Sends an existing invoice to the specified recipient. 45 | 46 | - `invoice_id` (string): The unique identifier of the invoice to be sent. 47 | 48 | > **Example Prompt**: 49 | > Send invoice {invoice_id} to the client 50 | 51 | --- 52 | 53 | **`send_invoice_reminder`** - Sends a reminder for an existing invoice. 54 | 55 | - `invoice_id` (string): The unique identifier of the invoice. 56 | 57 | > **Example Prompt**: 58 | > Send a reminder for invoice {invoice_id} 59 | 60 | --- 61 | 62 | **`cancel_sent_invoice`** - Cancels a sent invoice. 63 | 64 | - `invoice_id` (string): The unique identifier of the invoice to cancel. 65 | 66 | > **Example Prompt**: 67 | > Cancel the sent invoice {invoice_id} 68 | 69 | --- 70 | 71 | **`generate_invoice_qr_code`** - Generates a QR code for an invoice. 72 | 73 | - `invoice_id` (string): The unique identifier of the invoice. 74 | 75 | > **Example Prompt**: 76 | > Generate a QR code for invoice {invoice_id} 77 | 78 | ### **Payments** 79 | 80 | --- 81 | 82 | **`create_order`** - Creates an order in the PayPal system based on provided details. 83 | 84 | - `items` (array): List of items to include in the order. Each item should have. 85 | - `name` (string): Name of the item. 86 | - `quantity` (number): Quantity of the item. 87 | - `unit_price` (number): Price per unit of the item. 88 | - `currency` (string): Currency code (e.g., USD, EUR). 89 | 90 | > **Example Prompt**: 91 | > Place an order for {quantity} units of '{item_name}' at ${unit_price} each 92 | 93 | --- 94 | 95 | **`get_order`** - Retrieves the details of an order. 96 | 97 | - `order_id` (string): The unique identifier of the order. 98 | 99 | > **Example Prompt**: 100 | > Get details for order {order_id} 101 | 102 | --- 103 | 104 | **`pay_order`** - Captures payment for an authorized order. 105 | 106 | - `order_id` (string): The unique identifier of the order to capture. 107 | 108 | > **Example Prompt**: 109 | > Capture payment for order {order_id} 110 | 111 | ### **Dispute Management** 112 | 113 | --- 114 | 115 | **`list_disputes`** - Retrieves a summary of all disputes with optional filtering. 116 | 117 | - `status` (string, optional): Filter disputes by status (e.g., OPEN, RESOLVED). 118 | 119 | > **Example Prompt**: 120 | > List all {status} disputes 121 | 122 | --- 123 | 124 | **`get_dispute`** - Retrieves detailed information of a specific dispute. 125 | 126 | - `dispute_id` (string): The unique identifier of the dispute. 127 | 128 | > **Example Prompt**: 129 | > Get details for dispute {dispute_id} 130 | 131 | --- 132 | 133 | **`accept_dispute_claim`** - Accepts a dispute claim, resolving it in favor of the buyer. 134 | 135 | - `dispute_id` (string): The unique identifier of the dispute. 136 | 137 | > **Example Prompt**: 138 | > Accept the dispute with ID {dispute_id} 139 | 140 | ### **Shipment Tracking** 141 | 142 | --- 143 | 144 | **`create_shipment_tracking`** - Creates a shipment tracking record for a PayPal transaction. 145 | 146 | - `tracking_number` (string, required): The tracking number for the shipment. 147 | - `transaction_id` (string, required): The transaction ID associated with the shipment. 148 | - `carrier` (string, required): The carrier handling the shipment. (e.g.,FEDEX, UPS) 149 | - `order_id:` (string, optional): The order ID for which shipment needs to be created. 150 | - `status:` (string, optional, enum): The current status of the shipment. Allowed values are['ON_HOLD', 'SHIPPED', 'DELIVERED', 'CANCELLED', 'LOCAL_PICKUP'], default value: 'SHIPPED'. 151 | 152 | > **Example Prompt**: 153 | > Add tracking number '{tracking_number}' with carrier '{carrier}' to PayPal order ID - {order_id}. 154 | 155 | --- 156 | 157 | **`get_shipment_tracking`** - Gets shipment tracking information for a specific shipment. 158 | 159 | - `order_id` (string, required): The order ID for which shipment needs to be created. 160 | - `transaction_id` (string, optional): The transaction ID associated with the shipment. 161 | 162 | > **Example Prompt**: 163 | > Get the tracking number for PayPal order ID - {order_id} 164 | 165 | ### **Catalog Management** 166 | 167 | --- 168 | 169 | **`create_product`** - Creates a new product in the PayPal catalog. 170 | 171 | - `name` (string, required): The name of the product. 172 | - `type` (string, required, enum): The type of the product. Allowed values are ['PHYSICAL', 'DIGITAL', 'SERVICE']. 173 | 174 | > **Example Prompt**: 175 | > Create a new product with the name '{product_name}' of type '{product_type}'. 176 | 177 | --- 178 | 179 | **`list_products`** - Lists products from the PayPal catalog with optional pagination. 180 | 181 | - `page` (number, optional): The specific page number to retrieve. Defaults to the first page if not provided. 182 | - `page_size` (number, optional): The maximum number of products to return per page. Defaults to a system-defined limit if not provided. 183 | 184 | > **Example Prompt**: 185 | > List all products. 186 | 187 | --- 188 | 189 | **`show_product_details`** - Shows details of a specific product from the PayPal catalog. 190 | 191 | - `product_id` (string, required): The ID of the product to retrieve. 192 | 193 | > **Example Prompt**: 194 | > Show the details for product id {product_id}. 195 | 196 | ### **Subscription Management** 197 | 198 | --- 199 | 200 | **`create_subscription_plan`** - Creates a new subscription plan. 201 | 202 | - `product_id` (string, required): The name of the subscription plan. 203 | - `name` (string, required): The plan name. 204 | - `billing_cycles` (array, required): An array of billing cycles for trial billing and regular billing. A plan can have at most two trial cycles and only one regular cycle. 205 | - `tenure_type` (string, required): The type of billing cycle: [REGULAR|TRIAL]. 206 | - `sequence` (integer, required): The order in which this cycle is to run among other billing cycles. 207 | - `frequency`(integer, required): The frequency details for this billing cycle. 208 | - `interval_unit`(string, required): The interval at which the subscription is charged or billed.[DAY|WEEK|MONTH|YEAR] 209 | - `payment_preferences` (array, required): The payment preferences for a subscription. 210 | - `auto_bill_outstanding` (boolean, optional): Indicates whether to automatically bill the outstanding amount in the next billing cycle. Default:true 211 | 212 | > **Example Prompt**: 213 | > Create a {interval_unit} PayPal subscription plan for product '{product_name}' with billing cycle '{billing_cycle}', price '{price} {currency}'. Set trial period cycle to '{trial_period}'. 214 | 215 | --- 216 | 217 | **`list_subscription_plans`** - Lists subscription plans. 218 | 219 | - `product_id` (number, optional): List the subscription plans for a specific product. 220 | - `page` (number, optional): The specific page number to retrieve. Defaults to the first page if not provided. 221 | - `page_size` (number, optional): The maximum number of products to return per page. Defaults to a system-defined limit if not provided. 222 | 223 | > **Example Prompt**: 224 | > List all subscription plans. 225 | 226 | --- 227 | 228 | **`show_subscription_plan_details`** - Shows details of a specific subscription plan. 229 | 230 | - `billing_plan_id` (string, required): The ID of the subscription plan to retrieve. 231 | 232 | > **Example Prompt**: 233 | > Show the details for plan id {billing_plan_id}. 234 | 235 | --- 236 | 237 | **`create_subscription`** - Creates a new subscription. 238 | 239 | - `plan_id` (string, required): The ID of the subscription plan. 240 | - `Subscriber` (array, optional): The subscriber request information. 241 | - `name` (string, optional): The name of the subscriber. 242 | - `email` (string, Optional): The email address of the subscriber. 243 | 244 | > **Example Prompt**: 245 | > Create a subscription for plan id {plan_id} with subscriber name as {subscriber_name} with email address {subscriber_email}. 246 | 247 | --- 248 | 249 | **`show_subscription_details`** - Shows details of a specific subscription. 250 | 251 | - `subscription_id` (string, required): The ID of the subscription to retrieve. 252 | 253 | > **Example Prompt**: 254 | > Show the details for subscription id {subscription_id}. 255 | 256 | --- 257 | 258 | **`cancel_subscription`** - Cancels an active subscription. 259 | 260 | - `subscription_id` (string, required): The ID of the subscription to be cancelled. 261 | - `Reason` (string, optional): The reason for cancelling the subscription. 262 | 263 | > **Example Prompt**: 264 | > Cancel the subscription id {subscription_id} 265 | 266 | ### **Reporting and Insights** 267 | 268 | --- 269 | 270 | **`list_transaction`** - Lists all transactions with optional pagination and filtering. 271 | 272 | - `start_date` (string, optional): The start date for filtering transactions. Default value : 31 days 273 | - `end_date` (string, optional): The end date for filtering transactions. 274 | 275 | > **Example Prompt**: 276 | > Get the list of my transactions for last {days} days. 277 | -------------------------------------------------------------------------------- /modelcontextprotocol/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ -------------------------------------------------------------------------------- /modelcontextprotocol/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "bracketSpacing": false 5 | } 6 | -------------------------------------------------------------------------------- /modelcontextprotocol/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## PayPal Model Context Protocol 3 | 4 | The PayPal [Model Context Protocol](https://modelcontextprotocol.com/) server allows you to integrate with PayPal APIs through function calling. This protocol supports various tools to interact with different PayPal services. 5 | 6 | To run the PayPal MCP server using npx, use the following command: 7 | 8 | ```bash 9 | npx -y @paypal/mcp --tools=all PAYPAL_ACCESS_TOKEN="YOUR_ACCESS_TOKEN" PAYPAL_ENVIRONMENT="SANDBOX" 10 | ``` 11 | 12 | Replace `YOUR_ACCESS_TOKEN` with your PayPal access token. Refer this on how to [generate a PayPal access token](#generating-an-access-token). Alternatively, you could set the PAYPAL_ACCESS_TOKEN in your environment variables. 13 | 14 | ### Usage with MCP host (Claude Desktop/Cline/Cursor/GitHub Copilot) 15 | 16 | This guide explains how to integrate the PayPal connector with Claude Desktop. 17 | 18 | ## Prerequisites 19 | - Claude Desktop application installed 20 | - installing Node.js locally 21 | 22 | ## Installation Steps 23 | 24 | ### 1. Install Node.js 25 | 26 | Node.js is required for the PayPal connector to function: 27 | 28 | 1. Visit the [Node.js official website](https://nodejs.org/), download and install it. 29 | 2. Requirements: Node 18+ 30 | 31 | ### 2. Configure PayPal Connector with MCP host (Claude desktop / Cursor / Cline) 32 | We will show the integration with Claude desktop. You can use your favorite MCP host. 33 | 1. Open Claude Desktop 34 | 2. Navigate to Settings 35 | 3. Find the Developer or Advanced settings section 36 | 4. Locate the external tools or connectors configuration area 37 | 5. Add the following PayPal connector configuration to this ~/Claude/claude_desktop_config.json: 38 | 39 | ```json 40 | { 41 | "mcpServers": { 42 | "paypal": { 43 | "command": "npx", 44 | "args": [ 45 | "-y", 46 | "@paypal/mcp", 47 | "--tools=all" 48 | ], 49 | "env": { 50 | "PAYPAL_ACCESS_TOKEN": "YOUR_PAYPAL_ACCESS_TOKEN", 51 | "PAYPAL_ENVIRONMENT": "SANDBOX" 52 | } 53 | } 54 | } 55 | } 56 | ``` 57 | Make sure to replace `YOUR_PAYPAL_ACCESS_TOKEN` with your actual PayPal Access Token. Alternatively, you could set the PAYPAL_ACCESS_TOKEN as an environment variable. You can also pass it as an argument using --access-token in "args" 58 | Set `PAYPAL_ENVIRONMENT` value as either `SANDBOX` for stage testing and `PRODUCTION` for production environment. 59 | 60 | 6. Save your configuration changes 61 | 62 | ### 3. Test the Integration 63 | 64 | 1. Quit and restart Claude Desktop to apply changes 65 | 2. Test the connection by asking Claude to perform a PayPal-related task 66 | - Example: \"List my PayPal invoices\" 67 | 68 | ## Available tools 69 | 70 | **Invoices** 71 | 72 | - `create_invoice`: Create a new invoice in the PayPal system 73 | - `list_invoices`: List invoices with optional pagination and filtering 74 | - `get_invoice`: Retrieve details of a specific invoice 75 | - `send_invoice`: Send an invoice to recipients 76 | - `send_invoice_reminder`: Send a reminder for an existing invoice 77 | - `cancel_sent_invoice`: Cancel a sent invoice 78 | - `generate_invoice_qr_code`: Generate a QR code for an invoice 79 | 80 | **Payments** 81 | 82 | - `create_order`: Create an order in PayPal system based on provided details 83 | - `get_order`: Retrieve the details of an order 84 | - `pay_order`: Process payment for an authorized order 85 | 86 | **Dispute Management** 87 | 88 | - `list_disputes`: Retrieve a summary of all open disputes 89 | - `get_dispute`: Retrieve detailed information of a specific dispute 90 | - `accept_dispute_claim`: Accept a dispute claim 91 | 92 | **Shipment Tracking** 93 | 94 | - `create_shipment_tracking`: Create a shipment tracking record 95 | - `get_shipment_tracking`: Retrieve shipment tracking information 96 | 97 | **Catalog Management** 98 | 99 | - `create_product`: Create a new product in the PayPal catalog 100 | - `list_products`: List products with optional pagination and filtering 101 | - `show_product_details`: Retrieve details of a specific product 102 | - `update_product`: Update an existing product 103 | 104 | **Subscription Management** 105 | 106 | - `create_subscription_plan`: Create a new subscription plan 107 | - `list_subscription_plans`: List subscription plans 108 | - `show_subscription_plan_details`: Retrieve details of a specific subscription plan 109 | - `create_subscription`: Create a new subscription 110 | - `show_subscription_details`: Retrieve details of a specific subscription 111 | - `cancel_subscription`: Cancel an active subscription 112 | 113 | **Reporting and Insights** 114 | 115 | - `list_transactions`: List transactions with optional pagination and filtering 116 | 117 | ## Environment Variables 118 | 119 | The following environment variables can be used: 120 | 121 | - `PAYPAL_ACCESS_TOKEN`: Your PayPal Access Token 122 | - `PAYPAL_ENVIRONMENT`: Set to `SANDBOX` for sandbox mode, `PRODUCTION` for production (defaults to `SANDBOX` mode) 123 | 124 | 125 | This guide explains how to generate an access token for PayPal API integration, including how to find your client ID and client secret. 126 | 127 | 128 | 129 | ## Prerequisites 130 | 131 | - PayPal Developer account (for Sandbox) 132 | - PayPal Business account (for production) 133 | 134 | ## Finding Your Client ID and Client Secret 135 | 136 | 1. **Create a PayPal Developer Account**: 137 | - Go to [PayPal Developer Dashboard](https://developer.paypal.com/dashboard/) 138 | - Sign up or log in with your PayPal credentials 139 | 140 | 2. **Access Your Credentials**: 141 | - In the Developer Dashboard, click on **Apps & Credentials** in the menu 142 | - Switch between **Sandbox** and **Live** modes depending on your needs 143 | 144 | 3. **Create or View an App**: 145 | - To create a new app, click **Create App** 146 | - Give your app a name and select a Business account to associate with it 147 | - For existing apps, click on the app name to view details 148 | 149 | 4. **Retrieve Credentials**: 150 | - Once your app is created or selected, you'll see a screen with your: 151 | - **Client ID**: A public identifier for your app 152 | - **Client Secret**: A private key (shown after clicking \"Show\") 153 | - Save these credentials securely as they are required for generating access tokens 154 | 155 | ## Generating an Access Token 156 | ### Using cURL 157 | 158 | ```bash 159 | curl -v https://api-m.sandbox.paypal.com/v1/oauth2/token \\ 160 | -H \"Accept: application/json\" \\ 161 | -H \"Accept-Language: en_US\" \\ 162 | -u \"CLIENT_ID:CLIENT_SECRET\" \\ 163 | -d \"grant_type=client_credentials\" 164 | ``` 165 | 166 | Replace `CLIENT_ID` and `CLIENT_SECRET` with your actual credentials. For production, use `https://api-m.paypal.com` instead of the sandbox URL. 167 | 168 | 169 | ### Using Postman 170 | 171 | 1. Create a new request to `https://api-m.sandbox.paypal.com/v1/oauth2/token` 172 | 2. Set method to **POST** 173 | 3. Under **Authorization**, select **Basic Auth** and enter your Client ID and Client Secret 174 | 4. Under **Body**, select **x-www-form-urlencoded** and add a key `grant_type` with value `client_credentials` 175 | 5. Send the request 176 | 177 | ### Response 178 | 179 | A successful response will look like: 180 | 181 | ```json 182 | { 183 | "scope": "...", 184 | "access_token": "Your Access Token", 185 | "token_type": "Bearer", 186 | "app_id": "APP-80W284485P519543T", 187 | "expires_in": 32400, 188 | "nonce": "..." 189 | } 190 | ``` 191 | 192 | Copy the `access_token` value for use in your Claude Desktop integration. 193 | 194 | ## Token Details 195 | 196 | - **Sandbox Tokens**: Valid for 3-8 hours 197 | - **Production Tokens**: Valid for 8 hours 198 | - It's recommended to implement token refresh logic before expiration 199 | 200 | ## Using the Token with Claude Desktop 201 | 202 | Once you have your access token, update the `PAYPAL_ACCESS_TOKEN` value in your Claude Desktop connector configuration: 203 | 204 | ```json 205 | { 206 | "env": { 207 | "PAYPAL_ACCESS_TOKEN": "YOUR_NEW_ACCESS_TOKEN", 208 | "PAYPAL_ENVIRONMENT": "SANDBOX" 209 | } 210 | } 211 | ``` 212 | 213 | ## Best Practices 214 | 215 | 1. Store client ID and client secret securely 216 | 2. Implement token refresh logic to handle token expiration 217 | 3. Use environment-specific tokens (sandbox for testing, production for real transactions) 218 | 4. Avoid hardcoding tokens in application code 219 | 220 | ## Disclaimer 221 | `@paypal/mcp` *provides access to AI-generated content that may be inaccurate or incomplete. Users are responsible for independently verifying any information before relying on it. PayPal makes no guarantees regarding output accuracy and is not liable for any decisions, actions, or consequences resulting from its use.* 222 | -------------------------------------------------------------------------------- /modelcontextprotocol/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import prettier from "eslint-plugin-prettier"; 2 | import _import from "eslint-plugin-import"; 3 | import { fixupPluginRules } from "@eslint/compat"; 4 | import globals from "globals"; 5 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 6 | import path from "node:path"; 7 | import { fileURLToPath } from "node:url"; 8 | import js from "@eslint/js"; 9 | import { FlatCompat } from "@eslint/eslintrc"; 10 | 11 | const __filename = fileURLToPath(import.meta.url); 12 | const __dirname = path.dirname(__filename); 13 | const compat = new FlatCompat({ 14 | baseDirectory: __dirname, 15 | recommendedConfig: js.configs.recommended, 16 | allConfig: js.configs.all 17 | }); 18 | 19 | export default [...compat.extends("plugin:prettier/recommended"), { 20 | plugins: { 21 | prettier, 22 | import: fixupPluginRules(_import), 23 | }, 24 | 25 | languageOptions: { 26 | globals: { 27 | ...globals.node, 28 | }, 29 | 30 | ecmaVersion: 2018, 31 | sourceType: "commonjs", 32 | }, 33 | 34 | rules: { 35 | "accessor-pairs": "error", 36 | "array-bracket-spacing": ["error", "never"], 37 | "array-callback-return": "off", 38 | "arrow-parens": "error", 39 | "arrow-spacing": "error", 40 | "block-scoped-var": "off", 41 | "block-spacing": "off", 42 | 43 | "brace-style": ["error", "1tbs", { 44 | allowSingleLine: true, 45 | }], 46 | 47 | "capitalized-comments": "off", 48 | "class-methods-use-this": "off", 49 | "comma-dangle": "off", 50 | "comma-spacing": "off", 51 | "comma-style": ["error", "last"], 52 | complexity: "error", 53 | "computed-property-spacing": ["error", "never"], 54 | "consistent-return": "off", 55 | "consistent-this": "off", 56 | curly: "error", 57 | "default-case": "off", 58 | "dot-location": ["error", "property"], 59 | "dot-notation": "error", 60 | "eol-last": "error", 61 | eqeqeq: "off", 62 | "func-call-spacing": "error", 63 | "func-name-matching": "error", 64 | "func-names": "off", 65 | 66 | "func-style": ["error", "declaration", { 67 | allowArrowFunctions: true, 68 | }], 69 | 70 | "generator-star-spacing": "error", 71 | "global-require": "off", 72 | "guard-for-in": "error", 73 | "handle-callback-err": "off", 74 | "id-blacklist": "error", 75 | "id-length": "off", 76 | "id-match": "error", 77 | "import/extensions": "off", 78 | "init-declarations": "off", 79 | "jsx-quotes": "error", 80 | "key-spacing": "error", 81 | 82 | "keyword-spacing": ["error", { 83 | after: true, 84 | before: true, 85 | }], 86 | 87 | "line-comment-position": "off", 88 | "linebreak-style": ["error", "unix"], 89 | "lines-around-directive": "error", 90 | "max-depth": "error", 91 | "max-len": "off", 92 | "max-lines": "off", 93 | "max-nested-callbacks": "error", 94 | "max-params": "off", 95 | "max-statements": "off", 96 | "max-statements-per-line": "off", 97 | "multiline-ternary": "off", 98 | "new-cap": "off", 99 | "new-parens": "error", 100 | "newline-after-var": "off", 101 | "newline-before-return": "off", 102 | "newline-per-chained-call": "off", 103 | "no-alert": "error", 104 | "no-array-constructor": "error", 105 | "no-await-in-loop": "error", 106 | "no-bitwise": "off", 107 | "no-caller": "error", 108 | "no-catch-shadow": "off", 109 | "no-compare-neg-zero": "error", 110 | "no-confusing-arrow": "error", 111 | "no-continue": "off", 112 | "no-div-regex": "error", 113 | "no-duplicate-imports": "off", 114 | "no-else-return": "off", 115 | "no-empty-function": "off", 116 | "no-eq-null": "off", 117 | "no-eval": "error", 118 | "no-extend-native": "error", 119 | "no-extra-bind": "error", 120 | "no-extra-label": "error", 121 | "no-extra-parens": "off", 122 | "no-floating-decimal": "error", 123 | "no-implicit-globals": "error", 124 | "no-implied-eval": "error", 125 | "no-inline-comments": "off", 126 | "no-inner-declarations": ["error", "functions"], 127 | "no-invalid-this": "off", 128 | "no-iterator": "error", 129 | "no-label-var": "error", 130 | "no-labels": "error", 131 | "no-lone-blocks": "error", 132 | "no-lonely-if": "error", 133 | "no-loop-func": "error", 134 | "no-magic-numbers": "off", 135 | "no-mixed-requires": "error", 136 | "no-multi-assign": "off", 137 | "no-multi-spaces": "error", 138 | "no-multi-str": "error", 139 | "no-multiple-empty-lines": "error", 140 | "no-native-reassign": "error", 141 | "no-negated-condition": "off", 142 | "no-negated-in-lhs": "error", 143 | "no-nested-ternary": "error", 144 | "no-new": "error", 145 | "no-new-func": "error", 146 | "no-new-object": "error", 147 | "no-new-require": "error", 148 | "no-new-wrappers": "error", 149 | "no-octal-escape": "error", 150 | "no-param-reassign": "off", 151 | "no-path-concat": "error", 152 | 153 | "no-plusplus": ["error", { 154 | allowForLoopAfterthoughts: true, 155 | }], 156 | 157 | "no-process-env": "off", 158 | "no-process-exit": "error", 159 | "no-proto": "error", 160 | "no-prototype-builtins": "off", 161 | "no-restricted-globals": "error", 162 | "no-restricted-imports": "error", 163 | "no-restricted-modules": "error", 164 | "no-restricted-properties": "error", 165 | "no-restricted-syntax": "error", 166 | "no-return-assign": "error", 167 | "no-return-await": "error", 168 | "no-script-url": "error", 169 | "no-self-compare": "error", 170 | "no-sequences": "error", 171 | "no-shadow": "off", 172 | "no-shadow-restricted-names": "error", 173 | "no-spaced-func": "error", 174 | "no-sync": "error", 175 | "no-tabs": "error", 176 | "no-template-curly-in-string": "error", 177 | "no-ternary": "off", 178 | "no-throw-literal": "error", 179 | "no-trailing-spaces": "error", 180 | "no-undef-init": "error", 181 | "no-undefined": "off", 182 | "no-underscore-dangle": "off", 183 | "no-unmodified-loop-condition": "error", 184 | "no-unneeded-ternary": "error", 185 | "no-unused-expressions": "error", 186 | 187 | "no-unused-vars": ["error", { 188 | args: "none", 189 | }], 190 | 191 | "no-use-before-define": "off", 192 | "no-useless-call": "error", 193 | "no-useless-computed-key": "error", 194 | "no-useless-concat": "error", 195 | "no-useless-constructor": "error", 196 | "no-useless-escape": "off", 197 | "no-useless-rename": "error", 198 | "no-useless-return": "error", 199 | "no-var": "off", 200 | "no-void": "error", 201 | "no-warning-comments": "error", 202 | "no-whitespace-before-property": "error", 203 | "no-with": "error", 204 | "nonblock-statement-body-position": "error", 205 | "object-curly-newline": "off", 206 | "object-curly-spacing": ["error", "never"], 207 | "object-property-newline": "off", 208 | "object-shorthand": "off", 209 | "one-var": "off", 210 | "one-var-declaration-per-line": "error", 211 | "operator-assignment": ["error", "always"], 212 | "operator-linebreak": "off", 213 | "padded-blocks": "off", 214 | "prefer-arrow-callback": "off", 215 | "prefer-const": "error", 216 | 217 | "prefer-destructuring": ["error", { 218 | array: false, 219 | object: false, 220 | }], 221 | 222 | "prefer-numeric-literals": "error", 223 | "prefer-promise-reject-errors": "error", 224 | "prefer-reflect": "off", 225 | "prefer-rest-params": "off", 226 | "prefer-spread": "off", 227 | "prefer-template": "off", 228 | "quote-props": "off", 229 | 230 | quotes: ["error", "single", { 231 | avoidEscape: true, 232 | }], 233 | 234 | radix: "error", 235 | "require-await": "error", 236 | "require-jsdoc": "off", 237 | "rest-spread-spacing": "error", 238 | semi: "off", 239 | 240 | "semi-spacing": ["error", { 241 | after: true, 242 | before: false, 243 | }], 244 | 245 | "sort-imports": "off", 246 | "sort-keys": "off", 247 | "sort-vars": "error", 248 | "space-before-blocks": "error", 249 | "space-before-function-paren": "off", 250 | "space-in-parens": ["error", "never"], 251 | "space-infix-ops": "error", 252 | "space-unary-ops": "error", 253 | "spaced-comment": ["error", "always"], 254 | strict: "off", 255 | "symbol-description": "error", 256 | "template-curly-spacing": "error", 257 | "template-tag-spacing": "error", 258 | "unicode-bom": ["error", "never"], 259 | "valid-jsdoc": "off", 260 | "vars-on-top": "off", 261 | "wrap-regex": "off", 262 | "yield-star-spacing": "error", 263 | yoda: ["error", "never"], 264 | }, 265 | }, ...compat.extends( 266 | "eslint:recommended", 267 | "plugin:@typescript-eslint/eslint-recommended", 268 | "plugin:@typescript-eslint/recommended", 269 | "plugin:prettier/recommended", 270 | ).map(config => ({ 271 | ...config, 272 | files: ["**/*.ts"], 273 | })), { 274 | files: ["**/*.ts"], 275 | 276 | plugins: { 277 | "@typescript-eslint": typescriptEslint, 278 | prettier, 279 | }, 280 | 281 | rules: { 282 | "@typescript-eslint/no-use-before-define": 0, 283 | "@typescript-eslint/no-empty-interface": 0, 284 | "@typescript-eslint/no-unused-vars": 0, 285 | "@typescript-eslint/triple-slash-reference": 0, 286 | "@typescript-eslint/ban-ts-comment": "off", 287 | "@typescript-eslint/no-empty-function": 0, 288 | "@typescript-eslint/no-require-imports": 0, 289 | 290 | "@typescript-eslint/naming-convention": ["error", { 291 | selector: "default", 292 | format: ["camelCase", "UPPER_CASE", "PascalCase"], 293 | leadingUnderscore: "allow", 294 | }, { 295 | selector: "property", 296 | format: null, 297 | }], 298 | 299 | "@typescript-eslint/no-explicit-any": 0, 300 | "@typescript-eslint/explicit-function-return-type": "off", 301 | "@typescript-eslint/no-this-alias": "off", 302 | "@typescript-eslint/no-var-requires": 0, 303 | "prefer-rest-params": "off", 304 | }, 305 | }, { 306 | files: ["test/**/*.ts"], 307 | 308 | rules: { 309 | "@typescript-eslint/explicit-function-return-type": "off", 310 | }, 311 | }]; -------------------------------------------------------------------------------- /modelcontextprotocol/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type {Config} from 'jest'; 2 | 3 | const config: Config = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'node', 6 | roots: ['/src'], 7 | testMatch: ['**/test/**/*.ts?(x)'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | }; 10 | 11 | export default config; -------------------------------------------------------------------------------- /modelcontextprotocol/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paypal/mcp", 3 | "version": "0.3.3", 4 | "description": "A command line tool for setting up PayPal MCP server", 5 | "bin": "dist/index.js", 6 | "files": [ 7 | "dist/index.js", 8 | "LICENSE", 9 | "README.md", 10 | "VERSION", 11 | "package.json" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"", 15 | "clean": "rm -rf dist", 16 | "lint": "eslint \"./**/*.ts*\"", 17 | "prettier": "prettier './**/*.{js,ts,md,html,css}' --write", 18 | "prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check", 19 | "test": "jest" 20 | }, 21 | "packageManager": "pnpm@9.11.0", 22 | "engines": { 23 | "node": ">=18" 24 | }, 25 | "dependencies": { 26 | "@modelcontextprotocol/sdk": "^1.6.1", 27 | "colors": "^1.4.0", 28 | "@paypal/agent-toolkit": "latest" 29 | }, 30 | "imports": { 31 | "#sdk/*": "@modelcontextprotocol/sdk/dist/esm/*" 32 | }, 33 | "keywords": [ 34 | "mcp", 35 | "modelcontextprotocol", 36 | "paypal" 37 | ], 38 | "author": "PayPal (https://paypal.com/)", 39 | "devDependencies": { 40 | "@eslint/compat": "^1.2.6", 41 | "@types/colors": "^1.1.3", 42 | "@types/jest": "^29.5.14", 43 | "@types/node": "^22.13.4", 44 | "@typescript-eslint/eslint-plugin": "^8.24.1", 45 | "eslint-config-prettier": "^10.0.1", 46 | "eslint-plugin-import": "^2.31.0", 47 | "eslint-plugin-jest": "^28.11.0", 48 | "eslint-plugin-prettier": "^5.2.3", 49 | "globals": "^15.15.0", 50 | "jest": "^29.7.0", 51 | "prettier": "^3.5.1", 52 | "ts-jest": "^29.2.5", 53 | "ts-node": "^10.9.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /modelcontextprotocol/scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | RELEASE_TYPE=${1:-} 7 | 8 | echo_help() { 9 | cat << EOF 10 | USAGE: 11 | ./scripts/publish 12 | 13 | ARGS: 14 | 15 | A Semantic Versioning release type used to bump the version number. Either "patch", "minor", or "major". 16 | EOF 17 | } 18 | 19 | # Show help if no arguments passed 20 | if [ $# -eq 0 ]; then 21 | echo "Error! Missing release type argument" 22 | echo "" 23 | echo_help 24 | exit 1 25 | fi 26 | 27 | # Show help message if -h, --help, or help passed 28 | case $1 in 29 | -h | --help | help) 30 | echo_help 31 | exit 0 32 | ;; 33 | esac 34 | 35 | # Validate passed release type 36 | case $RELEASE_TYPE in 37 | patch | minor | major) 38 | ;; 39 | 40 | *) 41 | echo "Error! Invalid release type supplied" 42 | echo "" 43 | echo_help 44 | exit 1 45 | ;; 46 | esac 47 | 48 | # Make sure our working dir is the modelcontextprotocol directory 49 | cd "$(git rev-parse --show-toplevel)/modelcontextprotocol" 50 | 51 | echo "Fetching git remotes" 52 | git fetch 53 | 54 | GIT_STATUS=$(git status) 55 | 56 | if ! grep -q 'On branch main' <<< "$GIT_STATUS"; then 57 | echo "Error! Must be on main branch to publish" 58 | exit 1 59 | fi 60 | 61 | if ! grep -q "Your branch is up to date with 'origin/main'." <<< "$GIT_STATUS"; then 62 | echo "Error! Must be up to date with origin/main to publish" 63 | exit 1 64 | fi 65 | 66 | if ! grep -q 'working tree clean' <<< "$GIT_STATUS"; then 67 | echo "Error! Cannot publish with dirty working tree" 68 | exit 1 69 | fi 70 | 71 | echo "Installing dependencies according to lockfile" 72 | pnpm install --frozen-lockfile 73 | 74 | echo "Running tests" 75 | pnpm run test 76 | 77 | echo "Building package" 78 | pnpm run build 79 | 80 | echo "Publishing release" 81 | npm --ignore-scripts publish --non-interactive 82 | 83 | echo "Pushing git commit and tag" 84 | git push 85 | 86 | echo "Clean" 87 | pnpm run clean 88 | 89 | echo "Publish successful!" 90 | echo "" -------------------------------------------------------------------------------- /modelcontextprotocol/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import {PayPalAgentToolkit} from '@paypal/agent-toolkit/mcp'; 4 | import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'; 5 | import {green, red, yellow} from 'colors'; 6 | 7 | type ToolkitConfig = { 8 | actions: { 9 | [product: string]: {[action: string]: boolean}; 10 | }; 11 | context?: { 12 | account: string; 13 | }; 14 | }; 15 | 16 | type Options = { 17 | tools?: string[]; 18 | accessToken?: string; 19 | sandbox?: boolean; 20 | paypalAccount?: string; 21 | }; 22 | 23 | const ACCEPTED_ARGS = ['access-token', 'tools', 'paypal-environment']; 24 | const ACCEPTED_TOOLS = [ 25 | 'invoices.create', 26 | 'invoices.list', 27 | 'invoices.get', 28 | 'invoices.send', 29 | 'invoices.sendReminder', 30 | 'invoices.cancel', 31 | 'invoices.generateQRC', 32 | 'orders.create', 33 | 'orders.get', 34 | 'orders.capture', 35 | 'disputes.list', 36 | 'disputes.get', 37 | 'disputes.create', 38 | 'shipment.create', 39 | 'shipment.get', 40 | 'products.create', 41 | 'products.list', 42 | 'products.update', 43 | 'products.show', 44 | 'subscriptionPlans.create', 45 | 'subscriptionPlans.list', 46 | 'subscriptionPlans.show', 47 | 'subscriptions.create', 48 | 'subscriptions.show', 49 | 'subscriptions.cancel', 50 | 'transactions.list', 51 | ]; 52 | 53 | export function parseArgs(args: string[]): Options { 54 | const options: Options = {}; 55 | 56 | args.forEach((arg) => { 57 | if (arg.startsWith('--')) { 58 | const [key, value] = arg.slice(2).split('='); 59 | 60 | if (key == 'tools') { 61 | options.tools = value.split(','); 62 | } else if (key == 'access-token') { 63 | options.accessToken = value; 64 | } else if (key == 'paypal-environment') { 65 | options.sandbox = value.toLowerCase() != 'production'; 66 | } else { 67 | throw new Error( 68 | `Invalid argument: ${key}. Accepted arguments are: ${ACCEPTED_ARGS.join( 69 | ', ' 70 | )}` 71 | ); 72 | } 73 | } 74 | }); 75 | 76 | // Check if required tools arguments is present 77 | if (!options.tools) { 78 | throw new Error('The --tools arguments must be provided.'); 79 | } 80 | 81 | // Validate tools against accepted enum values 82 | options.tools.forEach((tool: string) => { 83 | if (tool == 'all') { 84 | return; 85 | } 86 | if (!ACCEPTED_TOOLS.includes(tool.trim())) { 87 | throw new Error( 88 | `Invalid tool: ${tool}. Accepted tools are: ${ACCEPTED_TOOLS.join( 89 | ', ' 90 | )}` 91 | ); 92 | } 93 | }); 94 | 95 | // Check if client credentials are either provided in args or set in environment variables 96 | const accessToken = options.accessToken || process.env.PAYPAL_ACCESS_TOKEN; 97 | if (!accessToken) { 98 | throw new Error( 99 | 'PayPal Access Token not provided. Please either pass it as an argument --access-token=$access_token_val or set the PAYPAL_ACCESS_TOKEN environment variable.' 100 | ); 101 | } 102 | options.accessToken = accessToken; 103 | 104 | // Set sandbox mode (default to true if not specified) 105 | if (options.sandbox === undefined) { 106 | const sandboxEnv = process.env.PAYPAL_ENVIRONMENT; 107 | options.sandbox = sandboxEnv 108 | ? sandboxEnv.toLowerCase() != 'production' 109 | : true; 110 | } 111 | 112 | return options; 113 | } 114 | 115 | function handleError(error: any) { 116 | console.error(red('\n🚨 Error initializing PayPal MCP server:\n')); 117 | console.error(yellow(` ${error.message}\n`)); 118 | } 119 | 120 | export async function main() { 121 | const options = parseArgs(process.argv.slice(2)); 122 | 123 | // Create the PayPalAgentToolkit instance 124 | const selectedTools = options.tools!; 125 | const configuration: ToolkitConfig = {actions: {}}; 126 | 127 | if (selectedTools.includes('all')) { 128 | ACCEPTED_TOOLS.forEach((tool) => { 129 | const [product, action] = tool.split('.'); 130 | configuration.actions[product] = { 131 | ...configuration.actions[product], 132 | [action]: true, 133 | }; 134 | }); 135 | } else { 136 | selectedTools.forEach((tool: any) => { 137 | const [product, action] = tool.split('.'); 138 | configuration.actions[product] = { 139 | ...configuration.actions[product], 140 | [action]: true, 141 | }; 142 | }); 143 | } 144 | 145 | // Append paypal account to configuration if provided 146 | if (options.paypalAccount) { 147 | configuration.context = {account: options.paypalAccount}; 148 | } 149 | 150 | // Create context for the PayPal API 151 | interface PayPalContext { 152 | accessToken: string; 153 | sandbox: boolean | undefined; 154 | merchant_id?: string; 155 | } 156 | 157 | const context: PayPalContext = { 158 | accessToken: options.accessToken!, 159 | sandbox: options.sandbox, 160 | }; 161 | 162 | // Add PayPal account to context if provided 163 | if (options.paypalAccount) { 164 | context.merchant_id = options.paypalAccount; 165 | } 166 | 167 | const server = new PayPalAgentToolkit({ 168 | accessToken: options.accessToken!, 169 | configuration: { 170 | ...configuration, 171 | context: context, 172 | }, 173 | }); 174 | 175 | const transport = new StdioServerTransport(); 176 | await server.connect(transport); 177 | // We use console.error instead of console.log since console.log will output to stdio, which will confuse the MCP server 178 | console.error(green('✅ PayPal MCP Server running on stdio')); 179 | console.error(green(` Mode: ${options.sandbox ? 'Sandbox' : 'Production'}`)); 180 | } 181 | 182 | if (require.main === module) { 183 | main().catch((error) => { 184 | handleError(error); 185 | }); 186 | } 187 | -------------------------------------------------------------------------------- /modelcontextprotocol/src/stdio-transport.ts: -------------------------------------------------------------------------------- 1 | // Create a minimal implementation matching what's needed 2 | import {Readable, Writable} from 'node:stream'; 3 | 4 | /** 5 | * Server transport for stdio: this communicates with a MCP client by reading from 6 | * the current process' stdin and writing to stdout. 7 | */ 8 | export class StdioServerTransport { 9 | private _stdin: Readable; 10 | private _stdout: Writable; 11 | private _readBuffer: Buffer; 12 | private _started: boolean; 13 | 14 | constructor(_stdin: Readable = process.stdin, _stdout: Writable = process.stdout) { 15 | this._stdin = _stdin; 16 | this._stdout = _stdout; 17 | this._readBuffer = Buffer.alloc(0); 18 | this._started = false; 19 | } 20 | 21 | onclose?: () => void; 22 | onerror?: (error: Error) => void; 23 | onmessage?: (message: any) => void; 24 | 25 | _ondata = (chunk: Buffer) => { 26 | // Add the chunk to the read buffer 27 | this._readBuffer = Buffer.concat([this._readBuffer, chunk]); 28 | this.processReadBuffer(); 29 | }; 30 | 31 | _onerror = (error: Error) => { 32 | if (this.onerror) { 33 | this.onerror(error); 34 | } 35 | }; 36 | 37 | /** 38 | * Starts listening for messages on stdin. 39 | */ 40 | async start(): Promise { 41 | if (this._started) { 42 | return; 43 | } 44 | this._started = true; 45 | 46 | this._stdin.on('data', this._ondata); 47 | this._stdin.on('error', this._onerror); 48 | } 49 | 50 | private processReadBuffer() { 51 | // Look for a Content-Length header 52 | const headerMatch = this._readBuffer.toString().match(/Content-Length: (\d+)\r\n\r\n/); 53 | if (!headerMatch) { 54 | return; 55 | } 56 | 57 | const contentLength = parseInt(headerMatch[1], 10); 58 | const headerEnd = headerMatch.index! + headerMatch[0].length; 59 | 60 | // Check if we have enough data to read the entire message 61 | if (this._readBuffer.length < headerEnd + contentLength) { 62 | return; 63 | } 64 | 65 | // Extract the message and remove it from the buffer 66 | const message = this._readBuffer.subarray(headerEnd, headerEnd + contentLength).toString(); 67 | this._readBuffer = this._readBuffer.subarray(headerEnd + contentLength); 68 | 69 | try { 70 | const parsed = JSON.parse(message); 71 | if (this.onmessage) { 72 | this.onmessage(parsed); 73 | } 74 | } catch (error) { 75 | if (this.onerror) { 76 | this.onerror(error instanceof Error ? error : new Error(String(error))); 77 | } 78 | } 79 | 80 | // Process any additional messages in the buffer 81 | if (this._readBuffer.length > 0) { 82 | this.processReadBuffer(); 83 | } 84 | } 85 | 86 | async close(): Promise { 87 | if (!this._started) { 88 | return; 89 | } 90 | this._started = false; 91 | 92 | this._stdin.off('data', this._ondata); 93 | this._stdin.off('error', this._onerror); 94 | 95 | if (this.onclose) { 96 | this.onclose(); 97 | } 98 | } 99 | 100 | async send(message: any): Promise { 101 | const content = JSON.stringify(message); 102 | const data = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n${content}`; 103 | this._stdout.write(data); 104 | } 105 | } -------------------------------------------------------------------------------- /modelcontextprotocol/src/types/stdio.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@modelcontextprotocol/sdk/server/stdio.js' { 2 | import { Readable, Writable } from 'node:stream'; 3 | import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types'; 4 | import { Transport } from '@modelcontextprotocol/sdk/shared/transport'; 5 | 6 | export class StdioServerTransport implements Transport { 7 | constructor(_stdin?: Readable, _stdout?: Writable); 8 | onclose?: () => void; 9 | onerror?: (error: Error) => void; 10 | onmessage?: (message: JSONRPCMessage) => void; 11 | start(): Promise; 12 | close(): Promise; 13 | send(message: JSONRPCMessage): Promise; 14 | } 15 | } -------------------------------------------------------------------------------- /modelcontextprotocol/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "outDir": "dist", 11 | "declaration": true, 12 | "paths": { 13 | "@modelcontextprotocol/sdk/server/stdio.js": ["./node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"] 14 | } 15 | }, 16 | "include": ["src/**/*"], 17 | "exclude": ["node_modules", "dist"] 18 | } -------------------------------------------------------------------------------- /python/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | ## [1.3.0] - 2025-04-23 5 | ### Added 6 | - Added support for PayPal Disputes, Shipment tracking and Transactions Search. 7 | - Improved and streamlined error logging mechanisms. 8 | 9 | ## [1.2.1] - 2025-04-23 10 | ### Added 11 | - Included User-Agent telemetry data for enhanced tracking. 12 | - Improved and streamlined error logging mechanisms. 13 | 14 | ## [1.2.0] - 2025-04-23 15 | ### Added 16 | - Added CrewAI support via `crewai.PayPalToolkit.get_tools()` for seamless use with CrewAI agents. 17 | - Refactored toolkit/shared code for improved structure and maintainability. 18 | 19 | ## [1.1.0] - 2025-04-21 20 | ### Added 21 | - Added support for PayPal Invoices. 22 | - Added LangChain integration with `langchain.PayPalToolkit.get_tools()` for LangChain agents. 23 | 24 | ## [1.0.1] - 2025-04-18 25 | ### Fixed 26 | - Handled `204 No Content` response formatting issue. 27 | - Fixed parameters formatting issue. 28 | 29 | ## [1.0.0] - 2025-04-18 30 | ### Added 31 | - First release of `paypal-agent-toolkit` 32 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # PayPal Agentic Toolkit 2 | 3 | The PayPal Agentic Toolkit integrates PayPal's REST APIs seamlessly with OpenAI, LangChain, CrewAI Agents, allowing AI-driven management of PayPal transactions. 4 | 5 | ## Available tools 6 | 7 | The PayPal Agent toolkit provides the following tools: 8 | 9 | **Invoices** 10 | 11 | - `create_invoice`: Create a new invoice in the PayPal system 12 | - `list_invoices`: List invoices with optional pagination and filtering 13 | - `get_invoice`: Retrieve details of a specific invoice 14 | - `send_invoice`: Send an invoice to recipients 15 | - `send_invoice_reminder`: Send a reminder for an existing invoice 16 | - `cancel_sent_invoice`: Cancel a sent invoice 17 | - `generate_invoice_qr_code`: Generate a QR code for an invoice 18 | 19 | **Payments** 20 | 21 | - `create_order`: Create an order in PayPal system based on provided details 22 | - `get_order`: Retrieve the details of an order 23 | - `pay_order`: Process payment for an authorized order 24 | 25 | **Dispute Management** 26 | 27 | - `list_disputes`: Retrieve a summary of all open disputes 28 | - `get_dispute`: Retrieve detailed information of a specific dispute 29 | - `accept_dispute_claim`: Accept a dispute claim 30 | 31 | **Shipment Tracking** 32 | 33 | - `create_shipment_tracking`: Create a shipment tracking record 34 | - `get_shipment_tracking`: Retrieve shipment tracking information 35 | 36 | **Catalog Management** 37 | 38 | - `create_product`: Create a new product in the PayPal catalog 39 | - `list_products`: List products with optional pagination and filtering 40 | - `show_product_details`: Retrieve details of a specific product 41 | 42 | **Subscription Management** 43 | 44 | - `create_subscription_plan`: Create a new subscription plan 45 | - `list_subscription_plans`: List subscription plans 46 | - `show_subscription_plan_details`: Retrieve details of a specific subscription plan 47 | - `create_subscription`: Create a new subscription 48 | - `show_subscription_details`: Retrieve details of a specific subscription 49 | - `cancel_subscription`: Cancel an active subscription 50 | 51 | **Reporting and Insights** 52 | 53 | - `list_transactions`: List transactions with optional pagination and filtering 54 | 55 | 56 | ## Prerequisites 57 | 58 | Before setting up the workspace, ensure you have the following installed: 59 | - Python 3.11 or higher 60 | - `pip` (Python package manager) 61 | - A PayPal developer account for API credentials 62 | 63 | ## Installation 64 | 65 | You don't need this source code unless you want to modify the package. If you just 66 | want to use the package, just run: 67 | 68 | ```sh 69 | pip install paypal-agent-toolkit 70 | ``` 71 | 72 | ## Configuration 73 | 74 | To get started, configure the toolkit with your PayPal API credentials from the [PayPal Developer Dashboard][app-keys]. 75 | 76 | ```python 77 | from paypal_agent_toolkit.shared.configuration import Configuration, Context 78 | 79 | configuration = Configuration( 80 | actions={ 81 | "orders": { 82 | "create": True, 83 | "get": True, 84 | "capture": True, 85 | } 86 | }, 87 | context=Context( 88 | sandbox=True 89 | ) 90 | ) 91 | 92 | ``` 93 | 94 | ### Logging Information 95 | The toolkit uses Python’s standard logging module to output logs. By default, logs are sent to the console. It is recommended to configure logging to a file to capture any errors or debugging information for easier troubleshooting. 96 | 97 | Recommendations: 98 | - Error Logging: Set the logging output to a file to ensure all errors are recorded. 99 | - Debugging Payloads/Headers: To see detailed request payloads and headers, set the logging level to DEBUG. 100 | 101 | ```python 102 | import logging 103 | 104 | # Basic configuration: logs to a file with INFO level 105 | logging.basicConfig( 106 | filename='paypal_agent_toolkit.log', 107 | level=logging.INFO, 108 | format='%(asctime)s %(levelname)s %(name)s %(message)s' 109 | ) 110 | 111 | # To enable debug-level logs (for seeing payloads and headers) 112 | # logging.getLogger().setLevel(logging.DEBUG) 113 | 114 | ``` 115 | 116 | 117 | ## Usage Examples 118 | 119 | This toolkit is designed to work with OpenAI's Agent SDK and Assistant API, langchain, crewai. It provides pre-built tools for managing PayPal transactions like creating, capturing, and checking orders details etc. 120 | 121 | ### OpenAI Agent 122 | ```python 123 | from agents import Agent, Runner 124 | from paypal_agent_toolkit.openai.toolkit import PayPalToolkit 125 | 126 | # Initialize toolkit 127 | toolkit = PayPalToolkit(PAYPAL_CLIENT_ID, PAYPAL_SECRET, configuration) 128 | tools = toolkit.get_tools() 129 | 130 | # Initialize OpenAI Agent 131 | agent = Agent( 132 | name="PayPal Assistant", 133 | instructions=""" 134 | You're a helpful assistant specialized in managing PayPal transactions: 135 | - To create orders, invoke create_order. 136 | - After approval by user, invoke pay_order. 137 | - To check an order status, invoke get_order_status. 138 | """, 139 | tools=tools 140 | ) 141 | # Initialize the runner to execute agent tasks 142 | runner = Runner() 143 | 144 | user_input = "Create an PayPal Order for $10 for AdsService" 145 | # Run the agent with user input 146 | result = await runner.run(agent, user_input) 147 | ``` 148 | 149 | 150 | ### OpenAI Assistants API 151 | ```python 152 | from openai import OpenAI 153 | from paypal_agent_toolkit.openai.toolkit import PayPalToolkit 154 | 155 | # Initialize toolkit 156 | toolkit = PayPalToolkit(client_id=PAYPAL_CLIENT_ID, secret=PAYPAL_SECRET, configuration = configuration) 157 | tools = toolkit.get_openai_chat_tools() 158 | paypal_api = toolkit.get_paypal_api() 159 | 160 | # OpenAI client 161 | client = OpenAI() 162 | 163 | # Create assistant 164 | assistant = client.beta.assistants.create( 165 | name="PayPal Checkout Assistant", 166 | instructions=f""" 167 | You help users create and process payment for PayPal Orders. When the user wants to make a purchase, 168 | use the create_order tool and share the approval link. After approval, use pay_order. 169 | """, 170 | model="gpt-4-1106-preview", 171 | tools=tools 172 | ) 173 | 174 | # Create a new thread for conversation 175 | thread = client.beta.threads.create() 176 | 177 | # Execute the assistant within the thread 178 | run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id) 179 | ``` 180 | 181 | ### LangChain Agent 182 | ```python 183 | from langchain.agents import initialize_agent, AgentType 184 | from langchain_openai import ChatOpenAI 185 | from paypal_agent_toolkit.langchain.toolkit import PayPalToolkit 186 | 187 | # Initialize Langchain Toolkit 188 | toolkit = PayPalToolkit(client_id=PAYPAL_CLIENT_ID, secret=PAYPAL_SECRET, configuration = configuration) 189 | tools = toolkit.get_tools() 190 | 191 | # Setup LangChain Agent 192 | agent = initialize_agent( 193 | tools=tools, 194 | llm=llm, 195 | agent=AgentType.OPENAI_FUNCTIONS, 196 | verbose=True 197 | ) 198 | 199 | prompt = "Create an PayPal order for $50 for Premium News service." 200 | # Run the agent with the defined prompt 201 | result = agent.run(prompt) 202 | ``` 203 | 204 | ### CrewAI Agent 205 | ```python 206 | from crewai import Agent, Crew, Task 207 | from paypal_agent_toolkit.crewai.toolkit import PayPalToolkit 208 | 209 | # Setup PayPal CrewAI Toolkit 210 | toolkit = PayPalToolkit(client_id=PAYPAL_CLIENT_ID, secret=PAYPAL_SECRET, configuration = configuration) 211 | tools = toolkit.get_tools() 212 | 213 | # Define an agent specialized in PayPal transactions 214 | agent = Agent( 215 | role="PayPal Assistant", 216 | goal="Help users create and manage PayPal transactions", 217 | backstory="You are a finance assistant skilled in PayPal operations.", 218 | tools=toolkit.get_tools(), 219 | allow_delegation=False 220 | ) 221 | 222 | # Define a CrewAI Task to create a PayPal order 223 | task = Task( 224 | description="Create an PayPal order for $50 for Premium News service.", 225 | expected_output="A PayPal order ID", 226 | agent=agent 227 | ) 228 | 229 | # Assemble Crew with defined agent and task 230 | crew = Crew(agents=[agent], tasks=[task], verbose=True, 231 | planning=True,) 232 | 233 | ``` 234 | 235 | ## Examples 236 | See /examples for ready-to-run samples using: 237 | 238 | - [OpenAI Agent SDK](https://github.com/paypal/agent-toolkit/tree/main/python/examples/openai/app_agent.py) 239 | - [Assistants API](https://github.com/paypal/agent-toolkit/tree/main/python/examples/openai/app_assistant_chatbot.py) 240 | - [LangChain integration](https://github.com/paypal/agent-toolkit/tree/main/python/examples/langchain/app_agent.py) 241 | - [CrewAI integration](https://github.com/paypal/agent-toolkit/tree/main/python/examples/crewai/app_agent.py) 242 | 243 | 244 | ## Disclaimer 245 | AI-generated content may be inaccurate or incomplete. Users are responsible for independently verifying any information before relying on it. PayPal makes no guarantees regarding output accuracy and is not liable for any decisions, actions, or consequences resulting from its use. 246 | 247 | [app-keys]: https://developer.paypal.com/dashboard/applications/sandbox 248 | -------------------------------------------------------------------------------- /python/examples/crewai/.env.sample: -------------------------------------------------------------------------------- 1 | # OpenAI Configuration 2 | OPENAI_API_KEY= 3 | OPENAI_API_VERSION= 4 | 5 | # PayPal Configuration 6 | PAYPAL_CLIENT_ID= 7 | PAYPAL_SECRET= 8 | 9 | -------------------------------------------------------------------------------- /python/examples/crewai/app_agent.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | warnings.filterwarnings("ignore", category=DeprecationWarning) 3 | 4 | import os 5 | import sys 6 | from crewai import Agent, Crew, Task 7 | 8 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) 9 | from paypal_agent_toolkit.crewai.toolkit import PayPalToolkit 10 | from paypal_agent_toolkit.shared.configuration import Configuration, Context 11 | 12 | 13 | #uncomment after setting the env file 14 | # load_dotenv() 15 | PAYPAL_CLIENT_ID = os.getenv("PAYPAL_CLIENT_ID") 16 | PAYPAL_SECRET = os.getenv("PAYPAL_CLIENT_SECRET") 17 | OPENAI_API_VERSION = "2024-02-15-preview" 18 | 19 | 20 | toolkit = PayPalToolkit( 21 | client_id=PAYPAL_CLIENT_ID, 22 | secret=PAYPAL_SECRET, 23 | configuration=Configuration( 24 | actions={"orders": {"create": True, "get": True, "capture": True}}, 25 | context=Context(sandbox=True) 26 | ) 27 | ) 28 | 29 | agent = Agent( 30 | role="PayPal Assistant", 31 | goal="Help users create and manage PayPal transactions", 32 | backstory="You are a finance assistant skilled in PayPal operations.", 33 | tools=toolkit.get_tools(), 34 | allow_delegation=False 35 | ) 36 | 37 | task = Task( 38 | description="Create an PayPal order for $50 for Premium News service.", 39 | expected_output="A PayPal order ID", 40 | agent=agent 41 | ) 42 | 43 | crew = Crew(agents=[agent], tasks=[task], verbose=True, 44 | planning=True,) 45 | 46 | result = crew.kickoff() 47 | print(result) 48 | -------------------------------------------------------------------------------- /python/examples/crewai/requirements.txt: -------------------------------------------------------------------------------- 1 | paypal-agent-toolkit 2 | # CrewAI 3 | crewai==0.76.2 4 | crewai-tools==0.13.2 5 | 6 | -------------------------------------------------------------------------------- /python/examples/langchain/.env.sample: -------------------------------------------------------------------------------- 1 | # OpenAI Configuration 2 | OPENAI_API_KEY= 3 | OPENAI_API_VERSION= 4 | 5 | # PayPal Configuration 6 | PAYPAL_CLIENT_ID= 7 | PAYPAL_SECRET= 8 | 9 | -------------------------------------------------------------------------------- /python/examples/langchain/app_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from langchain.agents import initialize_agent, AgentType 4 | from langchain_openai import ChatOpenAI 5 | 6 | from dotenv import load_dotenv 7 | 8 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) 9 | from paypal_agent_toolkit.langchain.toolkit import PayPalToolkit 10 | from paypal_agent_toolkit.shared.configuration import Configuration, Context 11 | 12 | 13 | #uncomment after setting the env file 14 | # load_dotenv() 15 | PAYPAL_CLIENT_ID = os.getenv("PAYPAL_CLIENT_ID") 16 | PAYPAL_SECRET = os.getenv("PAYPAL_CLIENT_SECRET") 17 | OPENAI_API_VERSION = "2024-02-15-preview" 18 | 19 | 20 | 21 | # --- STEP 1: Setup OpenAI LLM --- 22 | llm = ChatOpenAI( 23 | temperature=0.3, 24 | model="gpt-4o", # or "gpt-3.5-turbo" 25 | ) 26 | 27 | 28 | # --- STEP 2: Setup PayPal Configuration --- 29 | configuration = Configuration( 30 | actions={ 31 | "orders": { 32 | "create": True, 33 | "get": True, 34 | "capture": True, 35 | } 36 | }, 37 | context=Context( 38 | sandbox=True 39 | ) 40 | ) 41 | 42 | 43 | # --- STEP 3: Build PayPal Toolkit --- 44 | toolkit = PayPalToolkit(client_id=PAYPAL_CLIENT_ID, secret=PAYPAL_SECRET, configuration = configuration) 45 | tools = toolkit.get_tools() 46 | 47 | 48 | 49 | # --- STEP 4: Initialize LangChain Agent --- 50 | agent = initialize_agent( 51 | tools=tools, 52 | llm=llm, 53 | agent=AgentType.OPENAI_FUNCTIONS, 54 | verbose=True 55 | ) 56 | 57 | 58 | # --- STEP 5: Run Agent with Prompt --- 59 | if __name__ == "__main__": 60 | prompt = "Create an PayPal order for $50 for Premium News service." 61 | result = agent.run(prompt) 62 | print("Agent Output:", result) 63 | -------------------------------------------------------------------------------- /python/examples/langchain/requirements.txt: -------------------------------------------------------------------------------- 1 | paypal-agent-toolkit 2 | # LangChain 3 | langchain==0.3.23 4 | langchain-openai==0.2.2 5 | 6 | -------------------------------------------------------------------------------- /python/examples/openai/.env.sample: -------------------------------------------------------------------------------- 1 | # OpenAI Configuration 2 | OPENAI_API_KEY= 3 | OPENAI_API_VERSION= 4 | 5 | # PayPal Configuration 6 | PAYPAL_CLIENT_ID= 7 | PAYPAL_SECRET= 8 | 9 | -------------------------------------------------------------------------------- /python/examples/openai/app_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from openai import OpenAI 4 | import asyncio 5 | from dotenv import load_dotenv 6 | 7 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) 8 | from agents import Agent, Runner 9 | from paypal_agent_toolkit.openai.toolkit import PayPalToolkit 10 | from paypal_agent_toolkit.openai.tool import PayPalTool 11 | from paypal_agent_toolkit.shared.configuration import Configuration, Context 12 | 13 | #uncomment after setting the env file 14 | # load_dotenv() 15 | PAYPAL_CLIENT_ID = os.getenv("PAYPAL_CLIENT_ID") 16 | PAYPAL_SECRET = os.getenv("PAYPAL_CLIENT_SECRET") 17 | 18 | configuration = Configuration( 19 | actions={ 20 | "orders": { 21 | "create": True, 22 | "capture": True, 23 | "get": True 24 | }, 25 | "products": { 26 | "create": True, 27 | "list": True, 28 | "show": True 29 | }, 30 | "subscriptionPlans": { 31 | "create": True, 32 | "list": True, 33 | "show": True 34 | }, 35 | "subscriptions": { 36 | "create": True, 37 | "show": True, 38 | "cancel": True 39 | }, 40 | "invoices": { 41 | "create": True, 42 | "get": True, 43 | "list": True, 44 | "send": True, 45 | "sendReminder": True, 46 | "cancel": True, 47 | "generateQRC": True, 48 | }, 49 | "shipment": { 50 | "create": True, 51 | "get": True, 52 | "list": True 53 | }, 54 | "disputes": { 55 | "create": True, 56 | "get": True, 57 | "list": True 58 | }, 59 | "transactions": { 60 | "list": True 61 | }, 62 | }, 63 | context=Context( 64 | sandbox=True, 65 | ) 66 | ) 67 | 68 | 69 | # Initialize toolkit 70 | toolkit = PayPalToolkit(PAYPAL_CLIENT_ID, PAYPAL_SECRET, configuration) 71 | tools = toolkit.get_tools() 72 | 73 | 74 | # OpenAI client with SSL verify off 75 | client = OpenAI() 76 | 77 | # Initialize OpenAI Agent 78 | agent = Agent( 79 | name="PayPal Assistant", 80 | instructions=""" 81 | You are a helpful assistant that manages PayPal transactions. 82 | Help users with tasks like creating an order, capturing payment after approval, and checking order status. 83 | """, 84 | model="gpt-4o", 85 | tools=tools 86 | ) 87 | 88 | runner = Runner() 89 | 90 | # Step 1: Create Order 91 | # Step 2: Get Order Details (to show approval link) 92 | # Step 3: Wait for User to Approve 93 | # Step 4: Capture Payment 94 | async def main(): 95 | print("🚀 Starting PayPal Order Workflow...") 96 | 97 | try: 98 | # Step 1: Create Order 99 | user_prompt_create = "I want to buy a gaming keyboard for $100. Help me create the order." 100 | print("\n🛒 Step 1: Creating Order...") 101 | result_create = await runner.run(agent, user_prompt_create) 102 | 103 | if not hasattr(result_create, "final_output") or not result_create.final_output: 104 | raise Exception("Failed to create order.") 105 | 106 | order_id = result_create.final_output.strip() 107 | print(f"✅ Order Created: {order_id}") 108 | 109 | 110 | # Step 2: Get Order Details (to show approval link) 111 | user_prompt_details = f"Can you show me the details for my recent order ID: {order_id}?" 112 | print("\n🔎 Step 2: Retrieving Order Details...") 113 | result_details = await runner.run(agent, user_prompt_details) 114 | 115 | if not hasattr(result_details, "final_output") or not result_details.final_output: 116 | raise Exception("Failed to retrieve order details.") 117 | 118 | order_details = result_details.final_output.strip() 119 | print(f"\n📄 Order Details:\n{order_details}") 120 | 121 | print("\n🔗 Please approve the order using the approval URL shown above.") 122 | 123 | 124 | # Step 3: Wait for User to Approve 125 | input("\n⏳ Press Enter once buyer has approved the order in PayPal...") 126 | 127 | 128 | # Step 4: Capture Payment 129 | user_prompt_capture = f"I have approved the order. Can you process the payment for order ID: {order_id}?" 130 | print("\n💳 Step 4: Capturing Payment after approval...") 131 | result_capture = await runner.run(agent, user_prompt_capture) 132 | 133 | if not hasattr(result_capture, "final_output") or not result_capture.final_output: 134 | raise Exception("Failed to capture payment.") 135 | 136 | capture_response = result_capture.final_output.strip() 137 | print(f"✅ Payment Captured: {capture_response}") 138 | 139 | print("\n🏁 PayPal Order Workflow Completed Successfully.") 140 | 141 | except Exception as e: 142 | print(f"❌ Error during workflow: {e}") 143 | 144 | if __name__ == "__main__": 145 | try: 146 | asyncio.run(main()) 147 | except RuntimeError: 148 | loop = asyncio.get_event_loop() 149 | loop.run_until_complete(main()) -------------------------------------------------------------------------------- /python/examples/openai/app_assistant_chatbot.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import json 4 | import time 5 | import httpx 6 | import webbrowser 7 | import sys 8 | from openai import OpenAI 9 | from dotenv import load_dotenv 10 | 11 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) 12 | from paypal_agent_toolkit.openai.toolkit import PayPalToolkit 13 | from paypal_agent_toolkit.shared.configuration import Configuration, Context 14 | 15 | #uncomment after setting the env file 16 | # load_dotenv() 17 | PAYPAL_CLIENT_ID = os.getenv("PAYPAL_CLIENT_ID") 18 | PAYPAL_SECRET = os.getenv("PAYPAL_CLIENT_SECRET") 19 | OPENAI_API_VERSION = "2024-02-15-preview" 20 | 21 | configuration = Configuration( 22 | actions={ 23 | "orders": { 24 | "create": True, 25 | "get": True, 26 | "capture": True, 27 | } 28 | }, 29 | context=Context( 30 | sandbox=True 31 | ) 32 | ) 33 | 34 | # Initialize toolkit 35 | toolkit = PayPalToolkit(client_id=PAYPAL_CLIENT_ID, secret=PAYPAL_SECRET, configuration = configuration) 36 | tools = toolkit.get_openai_chat_tools() 37 | paypal_api = toolkit.get_paypal_api() 38 | 39 | 40 | # OpenAI client with SSL verify off 41 | client = OpenAI() 42 | 43 | # Create assistant 44 | assistant = client.beta.assistants.create( 45 | name="PayPal Checkout Assistant", 46 | instructions=f""" 47 | You help users create and process payment for PayPal Orders. When the user wants to make a purchase, 48 | use the create_order tool and share the approval link. After approval, use capture_order. 49 | """, 50 | model="gpt-4-1106-preview", 51 | tools=tools 52 | ) 53 | 54 | # Create thread 55 | thread = client.beta.threads.create() 56 | 57 | def run_agent(prompt: str): 58 | client.beta.threads.messages.create(thread_id=thread.id, role="user", content=prompt) 59 | run = client.beta.threads.runs.create(thread_id=thread.id, assistant_id=assistant.id) 60 | 61 | while True: 62 | run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id) 63 | if run.status == "completed": 64 | break 65 | elif run.status == "requires_action": 66 | outputs = [] 67 | for tool_call in run.required_action.submit_tool_outputs.tool_calls: 68 | method = tool_call.function.name 69 | args = tool_call.function.arguments 70 | # print(f"⚙️ Calling tool: {method} with args: {args}") 71 | result = json.loads(paypal_api.run( method, json.loads(args))) 72 | # print(f"🔧 Tool result: {result}") 73 | if method == "create_order": 74 | for link in result.get("links", []): 75 | if link["rel"] == "payer-action": 76 | result["checkout_url"] = link["href"] 77 | print(f"🟢 Approve URL: {link['href']}") 78 | webbrowser.open(link['href']) 79 | 80 | outputs.append({"tool_call_id": tool_call.id, "output": json.dumps(result)}) 81 | client.beta.threads.runs.submit_tool_outputs(thread_id=thread.id, run_id=run.id, tool_outputs=outputs) 82 | else: 83 | time.sleep(1) 84 | 85 | steps = client.beta.threads.runs.steps.list(thread_id=thread.id, run_id=run.id) 86 | for step in reversed(steps.data): 87 | if step.type == "message_creation": 88 | msg_id = step.step_details.message_creation.message_id 89 | msg = client.beta.threads.messages.retrieve(thread_id=thread.id, message_id=msg_id) 90 | print("🤖 Assistant:", msg.content[0].text.value) 91 | break 92 | 93 | 94 | def main(): 95 | print("💬 PayPal Assistant with Explicit Prompts (type 'exit' to quit)") 96 | 97 | while True: 98 | user_input = input("\nYou: ").strip() 99 | if user_input.lower() in {"exit", "quit"}: 100 | print("👋 Goodbye!") 101 | break 102 | run_agent(user_input) 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /python/examples/openai/requirements.txt: -------------------------------------------------------------------------------- 1 | paypal-agent-toolkit 2 | # OpenAI 3 | openai==1.66.0 4 | openai-agents==0.0.2 5 | 6 | -------------------------------------------------------------------------------- /python/examples/readme.md: -------------------------------------------------------------------------------- 1 | # PayPal Agent Toolkit Examples 2 | 3 | This example demonstrates how to use the PayPal Agentic Toolkit integrated with OpenAI, LangChain, and CrewAI to manage PayPal transactions through AI-powered applications. 4 | 5 | 6 | ## Prerequisites 7 | 8 | Before setting up the workspace, ensure you have the following installed: 9 | - Python 3.11 or higher 10 | - `pip` (Python package manager) 11 | - A PayPal developer account for API credentials 12 | 13 | ## Installing Dependencies 14 | 15 | Set up a Python virtual environment and install the required dependencies: 16 | ```bash 17 | # Step 1: Create a virtual environment 18 | python -m venv venv 19 | 20 | # Step 2: Activate the virtual environment 21 | source venv/bin/activate # On macOS/Linux 22 | # For Windows: 23 | # venv\Scripts\activate 24 | 25 | # Step 3: Install required dependencies 26 | pip install -r requirements.txt 27 | 28 | ``` 29 | 30 | ## Environment Setup 31 | Create and configure an .env file in the example directory to set your environment variables: 32 | ```env 33 | # OpenAI Configuration 34 | OPENAI_API_KEY= 35 | 36 | # PayPal Configuration 37 | PAYPAL_CLIENT_ID= 38 | PAYPAL_SECRET= 39 | 40 | ``` 41 | Replace the placeholders (, , etc.) with your actual credentials. 42 | 43 | ## Running the Example 44 | Navigate to the example directory and execute the assistant application: 45 | ```bash 46 | python app.py 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/agent-toolkit/8d55dda10dd79ac5ef22983ea29d10a9dfbd32c0/python/paypal_agent_toolkit/__init__.py -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/crewai/tool.py: -------------------------------------------------------------------------------- 1 | """ 2 | This tool allows agents to interact with the PayPal API. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from typing import Any, Optional, Type 8 | from pydantic import BaseModel 9 | 10 | from crewai_tools import BaseTool 11 | from ..shared.api import PayPalAPI 12 | 13 | 14 | class PayPalTool(BaseTool): 15 | """CrewAI-compatible tool for interacting with the PayPal API.""" 16 | 17 | paypal_api: PayPalAPI 18 | method: str 19 | name: str = "" 20 | description: str = "" 21 | args_schema: Optional[Type[BaseModel]] = None 22 | 23 | def _run( 24 | self, 25 | *args: Any, 26 | **kwargs: Any, 27 | ) -> str: 28 | """Use the PayPal API to run an operation.""" 29 | return self.paypal_api.run(self.method, kwargs) 30 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/crewai/toolkit.py: -------------------------------------------------------------------------------- 1 | """ 2 | PayPal Agent Toolkit for CrewAI. 3 | """ 4 | 5 | from typing import List, Optional 6 | from pydantic import PrivateAttr, BaseModel 7 | 8 | from ..shared.api import PayPalAPI 9 | from ..shared.tools import tools 10 | from ..shared.configuration import Configuration, is_tool_allowed 11 | from .tool import PayPalTool 12 | 13 | 14 | class PayPalToolkit: 15 | _tools: List = PrivateAttr() 16 | SOURCE = "CREWAI" 17 | def __init__( 18 | self, client_id: str, secret: str, configuration: Optional[Configuration] = None 19 | ): 20 | super().__init__() 21 | self._tools = [] 22 | self.context = configuration.context if configuration and configuration.context else Configuration.Context.default() 23 | self.context.source = self.SOURCE 24 | paypal_api = PayPalAPI(client_id=client_id, secret=secret, context=self.context) 25 | 26 | filtered_tools = [ 27 | tool for tool in tools if is_tool_allowed(tool, configuration) 28 | ] 29 | for tool in filtered_tools: 30 | args_schema = tool.get("args_schema") 31 | 32 | # Validate it's a subclass of BaseModel 33 | if args_schema and not issubclass(args_schema, BaseModel): 34 | raise TypeError(f"args_schema for '{tool['method']}' must be a Pydantic BaseModel") 35 | 36 | self._tools.append( 37 | PayPalTool( 38 | name=tool["method"], 39 | description=tool["description"], 40 | method=tool["method"], 41 | paypal_api=paypal_api, 42 | args_schema=args_schema, 43 | ) 44 | ) 45 | 46 | def get_tools(self) -> List: 47 | """Return a list of enabled PayPal tools.""" 48 | return self._tools 49 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/langchain/tool.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Tool for interacting with the PayPal API. 4 | """ 5 | 6 | from __future__ import annotations 7 | import json 8 | from typing import Any, Optional, Type 9 | from pydantic import BaseModel 10 | from langchain.tools import BaseTool 11 | 12 | from ..shared.api import PayPalAPI 13 | 14 | 15 | class PayPalTool(BaseTool): 16 | """ 17 | A LangChain-compatible tool that wraps a PayPal API method call. 18 | 19 | Attributes: 20 | paypal_api (PayPalAPI): An instance of the PayPal API client. 21 | method (str): The method name to invoke on the PayPal API. 22 | name (str): Human-readable name of the tool. 23 | description (str): Description of what the tool does. 24 | args_schema (Optional[Type[BaseModel]]): Optional argument schema for validation. 25 | """ 26 | 27 | paypal_api: PayPalAPI 28 | method: str 29 | name: str = "" 30 | description: str = "" 31 | args_schema: Optional[Type[BaseModel]] = None 32 | 33 | def _run(self, *args: Any, **kwargs: Any) -> str: 34 | """ 35 | Executes the configured PayPal API method. 36 | 37 | Returns: 38 | str: The result from the PayPal API, or an error message. 39 | """ 40 | try: 41 | return self.paypal_api.run(self.method, kwargs) 42 | except Exception as e: 43 | return f"Error executing PayPalTool '{self.method}': {str(e)}" 44 | 45 | def __repr__(self): 46 | return f"" 47 | 48 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/langchain/toolkit.py: -------------------------------------------------------------------------------- 1 | """PayPal Agent Toolkit.""" 2 | 3 | from typing import List, Optional 4 | from pydantic import PrivateAttr 5 | 6 | from ..shared.api import PayPalAPI 7 | from ..shared.tools import tools 8 | from ..shared.configuration import Configuration, Context, is_tool_allowed 9 | from .tool import PayPalTool 10 | 11 | 12 | class PayPalToolkit: 13 | """Toolkit for interacting with the PayPal API via tools.""" 14 | 15 | _tools: List = PrivateAttr(default=[]) 16 | SOURCE = "LANGCHAIN" 17 | def __init__(self, client_id, secret, configuration: Configuration): 18 | super().__init__() 19 | self.configuration = configuration 20 | self.context = configuration.context if configuration and configuration.context else Configuration.Context.default() 21 | self.context.source = self.SOURCE 22 | self._paypal_api = PayPalAPI(client_id=client_id, secret=secret, context=self.context) 23 | 24 | filtered_tools = [ 25 | tool for tool in tools if is_tool_allowed(tool, configuration) 26 | ] 27 | 28 | self._tools = [ 29 | PayPalTool( 30 | name=tool["method"], 31 | description=tool["description"], 32 | method=tool["method"], 33 | paypal_api=self._paypal_api, 34 | args_schema=tool.get("args_schema"), 35 | ) 36 | for tool in filtered_tools 37 | ] 38 | 39 | def get_tools(self) -> List[PayPalTool]: 40 | """Return a list of available PayPal tools.""" 41 | return self._tools 42 | 43 | def get_paypal_api(self) -> PayPalAPI: 44 | """Expose the underlying PayPal API client.""" 45 | return self._paypal_api 46 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/openai/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/agent-toolkit/8d55dda10dd79ac5ef22983ea29d10a9dfbd32c0/python/paypal_agent_toolkit/openai/__init__.py -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/openai/tool.py: -------------------------------------------------------------------------------- 1 | """PayPal Agentic Tools.""" 2 | import json 3 | from agents import FunctionTool 4 | from agents.run_context import RunContextWrapper 5 | 6 | from ..shared.api import PayPalAPI 7 | 8 | def PayPalTool(api: PayPalAPI, tool) -> FunctionTool: 9 | async def on_invoke_tool(ctx: RunContextWrapper, input_str: str) -> str: 10 | return api.run(tool["method"], json.loads(input_str)) 11 | 12 | parameters = tool["args_schema"].model_json_schema() 13 | 14 | # Enforce schema constraints 15 | parameters.update({ 16 | "additionalProperties": False, 17 | "type": "object" 18 | }) 19 | 20 | # Remove unnecessary metadata 21 | for key in ["description", "title"]: 22 | parameters.pop(key, None) 23 | 24 | # Clean up properties if they exist 25 | for prop in parameters.get("properties", {}).values(): 26 | for key in ["title", "default"]: 27 | prop.pop(key, None) 28 | 29 | return FunctionTool( 30 | name=tool["method"], 31 | description=tool["description"], 32 | params_json_schema=parameters, 33 | on_invoke_tool=on_invoke_tool, 34 | strict_json_schema=False 35 | ) -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/openai/toolkit.py: -------------------------------------------------------------------------------- 1 | """PayPal Agentic Toolkit.""" 2 | from typing import List 3 | from agents import FunctionTool 4 | from pydantic import PrivateAttr 5 | from ..shared.tools import tools 6 | from ..openai.tool import PayPalTool 7 | from ..shared.paypal_client import PayPalClient 8 | from ..shared.configuration import Configuration, is_tool_allowed 9 | from ..shared.api import PayPalAPI 10 | 11 | class PayPalToolkit: 12 | 13 | _tools: List[FunctionTool] = PrivateAttr(default=[]) 14 | _openai_tools = [] 15 | _paypal_api: PayPalAPI = PrivateAttr(default=None) 16 | SOURCE = "OPEN-AI" 17 | 18 | def __init__(self, client_id, secret, configuration: Configuration): 19 | self.configuration = configuration 20 | 21 | self.context = configuration.context if configuration and configuration.context else Configuration.Context.default() 22 | self.context.source = self.SOURCE 23 | self._paypal_api = PayPalAPI(client_id=client_id, secret=secret, context=self.context) 24 | 25 | filtered_tools = [ 26 | tool for tool in tools if is_tool_allowed(tool, configuration) 27 | ] 28 | 29 | self._tools = [ 30 | PayPalTool(self._paypal_api, tool) 31 | for tool in filtered_tools 32 | ] 33 | 34 | self._openai_tools = [ 35 | { 36 | "type": "function", 37 | "function": { 38 | "name": tool["method"], 39 | "description": tool["description"], 40 | "parameters": tool["args_schema"].model_json_schema(), 41 | } 42 | } 43 | for tool in filtered_tools 44 | ] 45 | 46 | def get_openai_chat_tools(self): 47 | """Get the tools in the openai chat assistant.""" 48 | return self._openai_tools 49 | 50 | def get_paypal_api(self): 51 | return self._paypal_api 52 | 53 | def get_tools(self) -> List[FunctionTool]: 54 | """Get the tools in the openai agent.""" 55 | return self._tools 56 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/agent-toolkit/8d55dda10dd79ac5ef22983ea29d10a9dfbd32c0/python/paypal_agent_toolkit/shared/__init__.py -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/api.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from typing import Optional 4 | from pydantic import BaseModel 5 | from .configuration import Context 6 | from .paypal_client import PayPalClient 7 | from .tools import tools 8 | 9 | class PayPalAPI(BaseModel): 10 | 11 | _context: Context 12 | _paypal_client: PayPalClient 13 | 14 | def __init__(self, client_id: str, secret: str, context: Optional[Context]): 15 | super().__init__() 16 | 17 | self._context = context if context is not None else Context() 18 | self._paypal_client = PayPalClient(client_id=client_id, secret=secret, context=context) 19 | 20 | 21 | def run(self, method: str, params: dict) -> str: 22 | for tool in tools: 23 | if tool.get("method") == method: 24 | execute_fn = tool.get("execute") 25 | if execute_fn: 26 | return execute_fn(self._paypal_client, params) 27 | raise ValueError(f"method: {method} not found in tools list") 28 | 29 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/configuration.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, Any 2 | 3 | class Context: 4 | 5 | @classmethod 6 | def default(cls) -> "Context": 7 | return cls(sandbox=True) 8 | 9 | def __init__( 10 | self, 11 | merchant_id: Optional[str] = None, 12 | sandbox: Optional[bool] = None, 13 | access_token: Optional[str] = None, 14 | request_id: Optional[str] = None, 15 | tenant_context: Optional[Any] = None, 16 | source: Optional[str] = None, 17 | **kwargs: Any 18 | ): 19 | self.merchant_id = merchant_id 20 | self.sandbox = sandbox or False 21 | self.access_token = access_token 22 | self.request_id = request_id 23 | self.tenant_context = tenant_context 24 | self.source = source or "OPEN-AI" 25 | self.extra = kwargs 26 | 27 | class Configuration: 28 | def __init__(self, actions: Dict[str, Dict[str, bool]], context: Optional[Context] = None): 29 | self.actions = actions 30 | self.context = context 31 | 32 | def is_tool_allowed(tool: Dict[str, Dict[str, Dict[str, bool]]], configuration: Configuration) -> bool: 33 | for product, product_actions in tool.get("actions", {}).items(): 34 | for action, allowed in product_actions.items(): 35 | if configuration.actions.get(product, {}).get(action, False): 36 | return True 37 | return False -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/constants.py: -------------------------------------------------------------------------------- 1 | SANDBOX_BASE_URL = "https://api-m.sandbox.paypal.com" 2 | LIVE_BASE_URL = "https://api-m.paypal.com" 3 | ENV_SANDBOX = "sandbox" 4 | ENV_LIVE = "live" -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/disputes/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional, Literal 3 | 4 | # === Disputes Parameters === 5 | 6 | class ListDisputesParameters(BaseModel): 7 | disputed_transaction_id: Optional[str] = None 8 | dispute_state: Optional[ 9 | Literal[ 10 | "REQUIRED_ACTION", 11 | "REQUIRED_OTHER_PARTY_ACTION", 12 | "UNDER_PAYPAL_REVIEW", 13 | "RESOLVED", 14 | "OPEN_INQUIRIES", 15 | "APPEALABLE" 16 | ] 17 | ] = Field(default=None, description="OPEN_INQUIRIES") 18 | page_size: Optional[int] = Field(default=10) 19 | 20 | 21 | class GetDisputeParameters(BaseModel): 22 | dispute_id: str = Field(..., description="The order id generated during create call") 23 | 24 | 25 | class AcceptDisputeClaimParameters(BaseModel): 26 | dispute_id: str 27 | note: str = Field(..., description="A note about why the seller is accepting the claim") 28 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/disputes/prompts.py: -------------------------------------------------------------------------------- 1 | LIST_DISPUTES_PROMPT = """ 2 | List disputes from PayPal. 3 | 4 | This function retrieves a list of disputes with optional pagination and filtering parameters. 5 | """ 6 | 7 | GET_DISPUTE_PROMPT = """ 8 | Get details for a specific dispute from PayPal. 9 | 10 | This tool is used to lists disputes with a summary set of details, which shows the dispute_id, reason, status, dispute_state, dispute_life_cycle_stage, dispute_channel, dispute_amount, create_time and update_time fields. 11 | """ 12 | 13 | ACCEPT_DISPUTE_CLAIM_PROMPT = """ 14 | Accept liability for a dispute claim. 15 | 16 | This tool is used to accept liability for a dispute claim. When you accept liability for a dispute claim, the dispute closes in the customer's favor and PayPal automatically refunds money to the customer from the merchant's account. 17 | """ -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/disputes/tool_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | from urllib.parse import urlencode 3 | from .parameters import * 4 | import json 5 | from typing import Union, Dict, Any 6 | 7 | 8 | 9 | def list_disputes(client, params: dict): 10 | 11 | validated = ListDisputesParameters(**params) 12 | query_string = urlencode(validated.dict(exclude_none=True)) 13 | uri = f"/v1/customer/disputes?{query_string}" 14 | 15 | response = client.get(uri=uri) 16 | return json.dumps(response) 17 | 18 | 19 | def get_dispute(client, params: dict): 20 | validated = GetDisputeParameters(**params) 21 | uri = f"/v1/customer/disputes/{validated.dispute_id}" 22 | 23 | response = client.get(uri=uri) 24 | return json.dumps(response) 25 | 26 | 27 | def accept_dispute_claim(client, params: dict): 28 | validated = AcceptDisputeClaimParameters(**params) 29 | uri = f"/v1/customer/disputes/{validated.dispute_id}/accept-claim" 30 | 31 | response = client.post(uri=uri, payload={"note": validated.note}) 32 | return json.dumps(response) 33 | 34 | 35 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/invoices/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import List, Optional, Literal 3 | 4 | 5 | class UnitAmount(BaseModel): 6 | currency_code: str = Field(..., description="Currency code of the unit amount") 7 | value: str = Field(..., description="The unit price. Up to 2 decimal points") 8 | 9 | 10 | class Tax(BaseModel): 11 | name: Optional[str] = Field(None, description="Tax name") 12 | percent: Optional[str] = Field(None, description="Tax Percent") 13 | 14 | 15 | class InvoiceItem(BaseModel): 16 | name: str = Field(..., description="The name of the item") 17 | quantity: str = Field(..., description="The quantity of the item that the invoicer provides to the payer. Value is from -1000000 to 1000000. Supports up to five decimal places. Cast to string") 18 | unit_amount: UnitAmount = Field(..., description="unit amount object") 19 | tax: Optional[Tax] = Field(None, description="tax object") 20 | unit_of_measure: Optional[Literal["QUANTITY", "HOURS", "AMOUNT"]] = Field(None, description="The unit of measure for the invoiced item") 21 | 22 | 23 | class InvoicerName(BaseModel): 24 | given_name: Optional[str] = Field(None, description="given name of the invoicer") 25 | surname: Optional[str] = Field(None, description="surname of the invoicer") 26 | 27 | 28 | class Invoicer(BaseModel): 29 | business_name: str = Field(..., max_length=300, description="business name of the invoicer") 30 | name: Optional[InvoicerName] = Field(None, description="name of the invoicer") 31 | email_address: Optional[str] = Field(None, description="email address of the invoicer") 32 | 33 | 34 | class RecipientName(BaseModel): 35 | given_name: Optional[str] = Field(None, description="given name of the recipient") 36 | surname: Optional[str] = Field(None, description="surname of the recipient") 37 | 38 | 39 | class BillingInfo(BaseModel): 40 | name: Optional[RecipientName] = Field(None, description="name of the recipient") 41 | email_address: Optional[str] = Field(None, description="email address of the recipient") 42 | 43 | 44 | class PrimaryRecipient(BaseModel): 45 | billing_info: Optional[BillingInfo] = Field(None, description="The billing information of the invoice recipient") 46 | 47 | 48 | class InvoiceDetail(BaseModel): 49 | invoice_date: Optional[str] = Field(None, description="The invoice date in YYYY-MM-DD format") 50 | currency_code: str = Field(..., description="currency code of the invoice") 51 | 52 | 53 | class CreateInvoiceParameters(BaseModel): 54 | detail: InvoiceDetail = Field(..., description="The invoice detail") 55 | invoicer: Optional[Invoicer] = Field(None, description="The invoicer business information that appears on the invoice.") 56 | primary_recipients: Optional[List[PrimaryRecipient]] = Field(None, description="array of recipients") 57 | items: Optional[List[InvoiceItem]] = Field(None, description="Array of invoice line items") 58 | 59 | class GetInvoiceParameters(BaseModel): 60 | invoice_id: str = Field(..., description="The ID of the invoice to retrieve.") 61 | 62 | 63 | class ListInvoicesParameters(BaseModel): 64 | page: Optional[int] = Field(1, description="The page number of the result set to fetch.") 65 | page_size: Optional[int] = Field(100, ge=1, le=100, description="The number of records to return per page (maximum 100).") 66 | total_required: Optional[bool] = Field(None, description="Indicates whether the response should include the total count of items.") 67 | 68 | 69 | class SendInvoiceParameters(BaseModel): 70 | invoice_id: str = Field(..., description="The ID of the invoice to send.") 71 | note: Optional[str] = Field(None, description="A note to the recipient.") 72 | send_to_recipient: Optional[bool] = Field(None, description="Indicates whether to send the invoice to the recipient.") 73 | additional_recipients: Optional[List[str]] = Field(None, description="Additional email addresses to which to send the invoice.") 74 | 75 | 76 | class SendInvoiceReminderParameters(BaseModel): 77 | invoice_id: str = Field(..., description="The ID of the invoice for which to send a reminder.") 78 | subject: Optional[str] = Field(None, description="The subject of the reminder email.") 79 | note: Optional[str] = Field(None, description="A note to the recipient.") 80 | additional_recipients: Optional[List[str]] = Field(None, description="Additional email addresses to which to send the reminder.") 81 | 82 | 83 | class CancelSentInvoiceParameters(BaseModel): 84 | invoice_id: str = Field(..., description="The ID of the invoice to cancel.") 85 | note: Optional[str] = Field(None, description="A cancellation note to the recipient.") 86 | send_to_recipient: Optional[bool] = Field(None, description="Indicates whether to send the cancellation to the recipient.") 87 | additional_recipients: Optional[List[str]] = Field(None, description="Additional email addresses to which to send the cancellation.") 88 | 89 | 90 | class GenerateInvoiceQrCodeParameters(BaseModel): 91 | invoice_id: str = Field(..., description="The invoice id to generate QR code for") 92 | width: int = Field(300, description="The QR code width") 93 | height: int = Field(300, description="The QR code height") 94 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/invoices/prompts.py: -------------------------------------------------------------------------------- 1 | CREATE_INVOICE_PROMPT = """ 2 | Create Invoices on PayPal. 3 | 4 | This function is used to create an invoice in the PayPal system. It allows you to generate a new invoice, specifying details such as customer information, items, quantities, pricing, and tax information. Once created, an invoice can be sent to the customer for payment. 5 | """ 6 | 7 | LIST_INVOICE_PROMPT = """ 8 | List invoices from PayPal. 9 | 10 | This function retrieves a list of invoices with optional pagination parameters. 11 | """ 12 | 13 | GET_INVOICE_PROMPT = """ 14 | Get an invoice from PayPal. 15 | 16 | This function retrieves details of a specific invoice using its ID. 17 | """ 18 | 19 | SEND_INVOICE_PROMPT = """ 20 | Send an invoice to the recipient(s). 21 | 22 | This function sends a previously created invoice to its intended recipients. 23 | """ 24 | 25 | SEND_INVOICE_REMINDER_PROMPT = """ 26 | Send a reminder for an invoice. 27 | 28 | This function sends a reminder for an invoice that has already been sent but hasn't been paid yet. 29 | """ 30 | 31 | CANCEL_SENT_INVOICE_PROMPT = """ 32 | Cancel a sent invoice. 33 | 34 | This function cancels an invoice that has already been sent to the recipient(s). 35 | """ 36 | 37 | GENERATE_INVOICE_QRCODE_PROMPT = """ 38 | Generate a QR code for an invoice. 39 | 40 | This function generates a QR code for an invoice, which can be used to pay the invoice using a mobile device or scanning app. 41 | """ -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/invoices/tool_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | from .parameters import * 3 | import json 4 | import httpx 5 | from typing import Union, Dict, Any 6 | 7 | 8 | 9 | def create_invoice(client, params: dict): 10 | 11 | validated = CreateInvoiceParameters(**params) 12 | invoice_payload = validated.model_dump() 13 | 14 | url = "/v2/invoicing/invoices" 15 | response = client.post(uri=url, payload=invoice_payload) 16 | 17 | if ( 18 | response.get("rel") == "self" 19 | and "/v2/invoicing/invoices/" in response.get("href", "") 20 | and response.get("method") == "GET" 21 | ): 22 | invoice_id = response["href"].split("/")[-1] 23 | try: 24 | send_result = send_invoice(client, { 25 | "invoice_id": invoice_id, 26 | "note": "Thank you for choosing us. If there are any issues, feel free to contact us.", 27 | "send_to_recipient": True 28 | }) 29 | return json.dumps({ 30 | "createResult": response, 31 | "sendResult": send_result 32 | }) 33 | except Exception: 34 | return json.dumps(response) 35 | 36 | return json.dumps(response) 37 | 38 | 39 | def send_invoice(client, params: dict): 40 | 41 | validated = SendInvoiceParameters(**params) 42 | payload = validated.model_dump() 43 | 44 | invoice_id = payload["invoice_id"] 45 | url = f"/v2/invoicing/invoices/{invoice_id}/send" 46 | 47 | response = client.post(uri=url, payload=payload) 48 | return json.dumps(response) 49 | 50 | 51 | def list_invoices(client, params: dict): 52 | 53 | validated = ListInvoicesParameters(**params) 54 | invoice_uri = f"/v2/invoicing/invoices?page_size={validated.page_size or 10}&page={validated.page or 1}&total_required={validated.total_required or 'true'}" 55 | response = client.get(uri=invoice_uri) 56 | 57 | return json.dumps(response) 58 | 59 | 60 | def get_invoice(client, params: dict): 61 | validated = GetInvoiceParameters(**params) 62 | invoice_id = validated.invoice_id 63 | 64 | url = f"/v2/invoicing/invoices/{invoice_id}" 65 | response = client.get(uri=url) 66 | 67 | return json.dumps(response) 68 | 69 | 70 | def send_invoice_reminder(client, params: dict): 71 | 72 | validated = SendInvoiceReminderParameters(**params) 73 | payload = validated.model_dump() 74 | 75 | invoice_id = payload["invoice_id"] 76 | url = f"/v2/invoicing/invoices/{invoice_id}/remind" 77 | print("url: ", url) 78 | response = client.post(uri=url, payload=payload) 79 | print("response: ", response) 80 | 81 | if response is None: 82 | return {"success": True, "invoice_id": invoice_id} 83 | return json.dumps(response) 84 | 85 | 86 | def cancel_sent_invoice(client, params: dict): 87 | 88 | validated = CancelSentInvoiceParameters(**params) 89 | payload = validated.model_dump() 90 | invoice_id = payload["invoice_id"] 91 | url = f"/v2/invoicing/invoices/{invoice_id}/cancel" 92 | 93 | response = client.post(uri=url, payload=payload) 94 | 95 | # PayPal responds with 204 No Content on successful cancellation 96 | if response is None: 97 | return {"success": True, "invoice_id": invoice_id} 98 | 99 | return json.dumps(response) 100 | 101 | 102 | def generate_invoice_qrcode(client, params: dict): 103 | 104 | validated = GenerateInvoiceQrCodeParameters(**params) 105 | payload = { 106 | "width": validated.width, 107 | "height": validated.height 108 | } 109 | 110 | invoice_id = validated.invoice_id 111 | url = f"/v2/invoicing/invoices/{invoice_id}/generate-qr-code" 112 | 113 | response = client.post(uri=url, payload=payload) 114 | 115 | if response is None: 116 | return {"success": True, "invoice_id": invoice_id} 117 | 118 | return json.dumps(response) -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/logger_util.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | import json 4 | 5 | def mask_bearer_token(token: str) -> str: 6 | if not token.startswith("Bearer "): 7 | return token 8 | raw = token[7:] # remove "Bearer " 9 | if len(raw) <= 8: 10 | return "Bearer ****" 11 | return f"Bearer {raw[:4]}****{raw[-4:]}" 12 | 13 | 14 | def logRequestPayload(payload, url, headers): 15 | logging.debug("PayPal POST %s", url) 16 | # Mask sensitive header before logging 17 | masked_headers = { 18 | **headers, 19 | "Authorization": mask_bearer_token(headers["Authorization"]) 20 | } 21 | logging.debug("PayPal Request Headers:\n%s", json.dumps(masked_headers, indent=2)) 22 | logging.debug("PayPal Request Payload:\n%s", json.dumps(payload, indent=2)) 23 | 24 | 25 | def logResponsePayload(response, json_response): 26 | request_headers = dict(response.request.headers) 27 | masked_headers = { 28 | **request_headers, 29 | "Authorization": mask_bearer_token(request_headers["Authorization"]) 30 | } 31 | logging.debug("PayPal Request Headers:\n%s", json.dumps(masked_headers, indent=2)) 32 | logging.debug("PayPal Response Headers: %s", json.dumps(dict(response.headers), indent=2)) 33 | logging.debug("PayPal Response Payload: %s", json.dumps(json_response, indent=2)) 34 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/orders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/agent-toolkit/8d55dda10dd79ac5ef22983ea29d10a9dfbd32c0/python/paypal_agent_toolkit/shared/orders/__init__.py -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/orders/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field, HttpUrl, validator, field_validator, ConfigDict, constr 2 | from typing import List, Literal, Optional 3 | 4 | 5 | 6 | class ItemDetails(BaseModel): 7 | item_cost: float = Field(..., description="The cost of each item – up to 2 decimal points.") 8 | tax_percent: float = Field(0, description="The tax percent for the specific item.") 9 | item_total: float = Field(..., description="The total cost of this line item.") 10 | 11 | 12 | class LineItem(ItemDetails): 13 | name: str = Field(..., description="The name of the item.") 14 | quantity: int = Field( 15 | 1, 16 | description="The item quantity. Must be a whole number.", 17 | ge=1 18 | ) 19 | description: Optional[str] = Field( 20 | None, 21 | description="The detailed item description." 22 | ) 23 | 24 | 25 | class ShippingAddress(BaseModel): 26 | address_line_1: Optional[str] = Field( 27 | None, 28 | description=( 29 | "The first line of the address, such as number and street, " 30 | "for example, `173 Drury Lane`. This field needs to pass the full address." 31 | ) 32 | ) 33 | address_line_2: Optional[str] = Field( 34 | None, 35 | description="The second line of the address, for example, a suite or apartment number." 36 | ) 37 | admin_area_2: Optional[str] = Field( 38 | None, 39 | description="A city, town, or village. Smaller than `admin_area_level_1`." 40 | ) 41 | admin_area_1: Optional[str] = Field( 42 | None, 43 | description=( 44 | "The highest-level sub-division in a country, which is usually a province, " 45 | "state, or ISO-3166-2 subdivision." 46 | ) 47 | ) 48 | postal_code: Optional[str] = Field( 49 | None, 50 | description=( 51 | "The postal code, which is the ZIP code or equivalent. Typically required " 52 | "for countries with a postal code or an equivalent." 53 | ) 54 | ) 55 | country_code: Optional[constr(min_length=2, max_length=2)] = Field( 56 | None, 57 | description=( 58 | "The 2-character ISO 3166-1 code that identifies the country or region. " 59 | "Note: The country code for Great Britain is `GB`." 60 | ) 61 | ) 62 | 63 | 64 | class CreateOrderParameters(BaseModel): 65 | model_config = ConfigDict(validate_default=True) 66 | currency_code: Literal["USD"] = Field( 67 | ..., 68 | description="Currency code of the amount." 69 | ) 70 | items: List[LineItem] = Field( 71 | ..., 72 | description="List of individual items in the order (max 50)." 73 | ) 74 | discount: float = Field( 75 | 0, 76 | description="The discount amount for the order." 77 | ) 78 | shipping_cost: float = Field( 79 | 0, 80 | description="The cost of shipping for the order." 81 | ) 82 | shipping_address: Optional[ShippingAddress] = Field( 83 | None, 84 | description="The shipping address for the order." 85 | ) 86 | notes: Optional[str] = Field( 87 | None, 88 | description="Optional customer notes or instructions." 89 | ) 90 | return_url: Optional[HttpUrl] = Field( 91 | "https://example.com/returnUrl", 92 | description="URL to redirect the buyer after approval." 93 | ) 94 | cancel_url: Optional[HttpUrl] = Field( 95 | "https://example.com/cancelUrl", 96 | description="URL to redirect the buyer if they cancel." 97 | ) 98 | 99 | 100 | class OrderIdParameters(BaseModel): 101 | order_id: str 102 | 103 | class CaptureOrderParameters(BaseModel): 104 | order_id: str 105 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/orders/payload_util.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | from urllib.parse import urlencode 4 | 5 | def parse_order_details(params: dict) -> dict: 6 | try: 7 | # use snake_case keys 8 | curr_code = params["currency_code"] 9 | items_in = params.get("items", []) 10 | shipping_cost = params.get("shipping_cost", 0) 11 | discount = params.get("discount", 0) 12 | 13 | sub_total = sum(item["item_cost"] * item.get("quantity", 1) for item in items_in) 14 | tax_amount = sum( 15 | item["item_cost"] * item.get("tax_percent", 0) * item.get("quantity", 1) / 100 16 | for item in items_in 17 | ) 18 | total = sub_total + tax_amount + shipping_cost - discount 19 | 20 | amount_breakdown = { 21 | "item_total": { 22 | "value": f"{sub_total:.2f}", 23 | "currency_code": curr_code, 24 | }, 25 | "shipping": { 26 | "value": f"{shipping_cost:.2f}", 27 | "currency_code": curr_code, 28 | }, 29 | "tax_total": { 30 | "value": f"{tax_amount:.2f}", 31 | "currency_code": curr_code, 32 | }, 33 | "discount": { 34 | "value": f"{discount:.2f}", 35 | "currency_code": curr_code, 36 | }, 37 | } 38 | 39 | items = [] 40 | for item in items_in: 41 | items.append({ 42 | "name": item["name"], 43 | "description": item.get("description", ""), 44 | "unit_amount": { 45 | "value": f"{item['item_cost']:.2f}", 46 | "currency_code": curr_code, 47 | }, 48 | "quantity": str(item.get("quantity", 1)), 49 | "tax": { 50 | "value": f"{(item['item_cost'] * item.get('tax_percent', 0) / 100):.2f}", 51 | "currency_code": curr_code, 52 | } 53 | }) 54 | 55 | base_purchase_unit = { 56 | "amount": { 57 | "value": f"{total:.2f}", 58 | "currency_code": curr_code, 59 | "breakdown": amount_breakdown, 60 | }, 61 | "items": items, 62 | } 63 | 64 | if params.get("shipping_address"): 65 | base_purchase_unit["shipping"] = {"address": params["shipping_address"]} 66 | 67 | request = { 68 | "intent": "CAPTURE", 69 | "purchase_units": [base_purchase_unit], 70 | } 71 | 72 | experience_context = {} 73 | if params.get("return_url"): 74 | experience_context["return_url"] = str(params["return_url"]) 75 | if params.get("cancel_url"): 76 | experience_context["cancel_url"] = str(params["cancel_url"]) 77 | 78 | if experience_context: 79 | request["payment_source"] = { 80 | "paypal": { 81 | "experience_context": experience_context 82 | } 83 | } 84 | 85 | return request 86 | 87 | except Exception as e: 88 | logging.error("parse_order_details error:", e) 89 | raise ValueError("Failed to parse order details") from e 90 | 91 | 92 | 93 | def to_snake_case_keys(obj): 94 | """ 95 | Recursively convert dict keys from camelCase (or mixed) to snake_case. 96 | """ 97 | if isinstance(obj, list): 98 | return [to_snake_case_keys(v) for v in obj] 99 | elif isinstance(obj, dict): 100 | new = {} 101 | for k, v in obj.items(): 102 | # insert underscore before capital letters, then lower() 103 | new_key = re.sub(r"(? str: 128 | """ 129 | Build a URL query string from a flat dict, ignoring None values. 130 | """ 131 | filtered = {k: v for k, v in params.items() if v is not None} 132 | # urlencode will properly escape keys and values 133 | return urlencode({k: str(v) for k, v in filtered.items()}) 134 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/orders/prompts.py: -------------------------------------------------------------------------------- 1 | CREATE_ORDER_PROMPT = """ 2 | Use this tool to create a PayPal order when the user intends to purchase goods or services. 3 | 4 | This tool is used to create a new order in PayPal. This is typically the first step in initiating a payment flow. It sets up an order with specified details such as item(s) to be purchased, quantity, amount, currency, and other details. 5 | """ 6 | 7 | CAPTURE_ORDER_PROMPT = """ 8 | Use this tool after the user has approved the PayPal order. 9 | 10 | Paramters: 11 | - order_id (str, required): The PayPal order ID provided after the user has approved the payment. 12 | It typically appears in the approval link or as a token after payment approval. 13 | """ 14 | 15 | GET_ORDER_PROMPT = """ 16 | Use this tool to retrieve the current status of a PayPal order. 17 | 18 | Paramters: 19 | - order_id (str, required): The PayPal order ID you want to check. 20 | """ 21 | 22 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/orders/tool_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | from .parameters import * 3 | from .payload_util import parse_order_details 4 | import json 5 | 6 | def create_order(client, params: dict): 7 | 8 | validated = CreateOrderParameters(**params) 9 | order_payload = parse_order_details(validated.model_dump()) 10 | 11 | order_uri = "/v2/checkout/orders" 12 | response = client.post(uri=order_uri, payload=order_payload) 13 | return json.dumps(response) 14 | 15 | 16 | 17 | def capture_order(client, params: dict): 18 | validated = CaptureOrderParameters(**params) 19 | order_capture_uri = f"/v2/checkout/orders/{validated.order_id}/capture" 20 | result = client.post(uri=order_capture_uri, payload=None) 21 | status = result.get("status") 22 | amount = result.get("purchase_units", [{}])[0].get("payments", {}).get("captures", [{}])[0].get("amount", {}).get("value") 23 | currency = result.get("purchase_units", [{}])[0].get("payments", {}).get("captures", [{}])[0].get("amount", {}).get("currency_code") 24 | 25 | return json.dumps({ 26 | "message": f"The PayPal order {validated.order_id} has been successfully captured.", 27 | "status": status, 28 | "amount": f"{currency} {amount}" if amount and currency else "N/A", 29 | "raw": result 30 | }) 31 | 32 | 33 | def get_order_details(client, params: dict): 34 | validated = OrderIdParameters(**params) 35 | order_get_uri = f"/v2/checkout/orders/{validated.order_id}" 36 | 37 | result = client.get(order_get_uri) 38 | status = result.get("status") 39 | amount = result.get("purchase_units", [{}])[0].get("payments", {}).get("captures", [{}])[0].get("amount", {}).get("value") 40 | currency = result.get("purchase_units", [{}])[0].get("payments", {}).get("captures", [{}])[0].get("amount", {}).get("currency_code") 41 | 42 | return json.dumps({ 43 | "message": f"The PayPal order {validated.order_id} has been successfully captured.", 44 | "status": status, 45 | "amount": f"{currency} {amount}" if amount and currency else "N/A", 46 | "raw": result 47 | }) -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/paypal_client.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Optional 3 | import requests 4 | 5 | from ..shared.telemetry import Telemetry 6 | 7 | from .logger_util import logRequestPayload, logResponsePayload 8 | from .constants import * 9 | from .configuration import Context 10 | import logging 11 | 12 | 13 | class PayPalClient: 14 | def __init__(self, client_id, secret, context: Optional[Context]): 15 | self.client_id = client_id 16 | self.secret = secret 17 | self.context = context 18 | self.sandbox = context.sandbox 19 | self.base_url = SANDBOX_BASE_URL if self.sandbox else LIVE_BASE_URL 20 | 21 | 22 | 23 | def log_request_exception(self, e: requests.exceptions.RequestException, url: Optional[str] = None): 24 | response = getattr(e, 'response', None) 25 | if response is not None: 26 | try: 27 | logging.error("PayPal Error Response: %s", json.dumps(response.json(), indent=2)) 28 | except ValueError: 29 | logging.error("PayPal Error Response: Not valid JSON") 30 | logging.error("Response Headers: %s", json.dumps(dict(response.headers), indent=2)) 31 | if url: 32 | logging.error("Request to %s failed: %s", url, str(e)) 33 | else: 34 | logging.error("HTTP request failed: %s", str(e)) 35 | 36 | 37 | def build_headers(self): 38 | headers = { 39 | "Authorization": f"Bearer {self.get_access_token()}", 40 | "Content-Type": "application/json", 41 | "User-Agent" : Telemetry.generate_user_agent(source=self.context.source) 42 | } 43 | return headers 44 | 45 | def get_access_token(self): 46 | token_url = f"{self.base_url}/v1/oauth2/token" 47 | try: 48 | response = requests.post( 49 | token_url, 50 | headers={"Accept": "application/json"}, 51 | data={"grant_type": "client_credentials"}, 52 | auth=(self.client_id, self.secret) 53 | ) 54 | response.raise_for_status() 55 | except requests.exceptions.RequestException as e: 56 | self.log_request_exception(e, token_url) 57 | raise RuntimeError("Failed to obtain access token from PayPal") from e 58 | 59 | 60 | logging.debug("PayPal Response Headers: %s", json.dumps(dict(response.headers), indent=2)) 61 | 62 | token_data = response.json() 63 | if "access_token" not in token_data: 64 | raise ValueError("Access token not found in PayPal response") 65 | 66 | return token_data["access_token"] 67 | 68 | 69 | def post(self, uri, payload): 70 | 71 | url = f"{self.base_url}{uri}" 72 | headers = self.build_headers() 73 | logRequestPayload(payload, url, headers) 74 | 75 | try: 76 | response = requests.post(url, headers=headers, json=payload) 77 | response.raise_for_status() 78 | except requests.exceptions.RequestException as e: 79 | self.log_request_exception(e, url) 80 | raise 81 | 82 | if response.status_code == 204: 83 | logging.debug("Response Status: 204 No Content") 84 | return {} 85 | 86 | try: 87 | json_response = response.json() 88 | except ValueError: 89 | logging.warning("Response body is not valid JSON or empty, Headers: %s", json.dumps(dict(response.headers), indent=2)) 90 | return {} 91 | 92 | logResponsePayload(response, json_response) 93 | 94 | return json_response 95 | 96 | 97 | 98 | def get(self, uri): 99 | 100 | url = f"{self.base_url}{uri}" 101 | headers = self.build_headers() 102 | 103 | logRequestPayload( None, url, headers) 104 | 105 | try: 106 | response = requests.get(url, headers=headers) 107 | response.raise_for_status() 108 | except requests.exceptions.RequestException as e: 109 | self.log_request_exception(e, url) 110 | logging.error("HTTP request failed: %s", str(e)) 111 | raise 112 | 113 | try: 114 | json_response = response.json() 115 | except ValueError: 116 | logging.warning("Response body is not valid JSON or empty") 117 | return {} 118 | 119 | logResponsePayload(response, json_response) 120 | 121 | return json_response 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/subscriptions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paypal/agent-toolkit/8d55dda10dd79ac5ef22983ea29d10a9dfbd32c0/python/paypal_agent_toolkit/shared/subscriptions/__init__.py -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/subscriptions/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field, HttpUrl, validator, field_validator, ConfigDict, constr 2 | from typing import List, Literal, Optional 3 | 4 | 5 | class CreateProductParameters(BaseModel): 6 | name: str 7 | type: Literal['PHYSICAL', 'DIGITAL', 'SERVICE'] # Enum-like behavior for product type 8 | description: Optional[str] = None 9 | category: Optional[str] = None 10 | image_url: Optional[HttpUrl] = None # Ensures valid URL 11 | home_url: Optional[HttpUrl] = None # Ensures valid URL 12 | 13 | class ListProductsParameters(BaseModel): 14 | page: Optional[int] = None 15 | page_size: Optional[int] = None 16 | total_required: Optional[bool] = None 17 | 18 | class ShowProductDetailsParameters(BaseModel): 19 | product_id: str 20 | 21 | # Frequency Schema 22 | class FrequencySchema(BaseModel): 23 | interval_unit: Literal['DAY', 'WEEK', 'MONTH', 'YEAR'] = Field(..., description="The unit of time for the billing cycle.") 24 | interval_count: int = Field(..., description="The number of units for the billing cycle.") 25 | 26 | # Pricing Scheme Schema 27 | class FixedPriceSchema(BaseModel): 28 | currency_code: Literal['USD'] = Field(..., description="The currency code for the fixed price.") 29 | value: str = Field(..., description="The value of the fixed price.") 30 | 31 | class PricingSchemeSchema(BaseModel): 32 | fixed_price: Optional[FixedPriceSchema] = Field(None, description="The fixed price for the subscription plan.") 33 | version: Optional[str] = Field(None, description="The version of the pricing scheme.") 34 | 35 | # Billing Cycle Schema 36 | class BillingCycleSchema(BaseModel): 37 | frequency: FrequencySchema = Field(..., description="The frequency of the billing cycle.") 38 | tenure_type: Literal['REGULAR', 'TRIAL'] = Field(..., description="The type of billing cycle tenure.") 39 | sequence: int = Field(..., description="The sequence of the billing cycle.") 40 | total_cycles: Optional[int] = Field(None, description="The total number of cycles in the billing plan.") 41 | pricing_scheme: PricingSchemeSchema = Field(..., description="The pricing scheme for the billing cycle.") 42 | 43 | # Setup Fee Schema 44 | class SetupFeeSchema(BaseModel): 45 | currency_code: Optional[Literal['USD']] = Field(None, description="The currency code for the setup fee.") 46 | value: Optional[str] = Field(None, description="The value of the setup fee.") 47 | 48 | # Payment Preferences Schema 49 | class PaymentPreferencesSchema(BaseModel): 50 | auto_bill_outstanding: Optional[bool] = Field(None, description="Indicates whether to automatically bill outstanding amounts.") 51 | setup_fee: Optional[SetupFeeSchema] = Field(None, description="The setup fee for the subscription plan.") 52 | setup_fee_failure_action: Optional[Literal['CONTINUE', 'CANCEL']] = Field(None, description="The action to take if the setup fee payment fails.") 53 | payment_failure_threshold: Optional[int] = Field(None, description="The number of failed payments before the subscription is canceled.") 54 | 55 | # Taxes Schema 56 | class TaxesSchema(BaseModel): 57 | percentage: Optional[str] = Field(None, description="The tax percentage.") 58 | inclusive: Optional[bool] = Field(None, description="Indicates whether the tax is inclusive.") 59 | 60 | # Create Subscription Plan Parameters 61 | class CreateSubscriptionPlanParameters(BaseModel): 62 | product_id: str = Field(..., description="The ID of the product for which to create the plan.") 63 | name: str = Field(..., description="The subscription plan name.") 64 | description: Optional[str] = Field(None, description="The subscription plan description.") 65 | billing_cycles: List[BillingCycleSchema] = Field(..., description="The billing cycles of the plan.") 66 | payment_preferences: PaymentPreferencesSchema = Field(..., description="The payment preferences for the subscription plan.") 67 | taxes: Optional[TaxesSchema] = Field(None, description="The tax details.") 68 | 69 | # List Subscription Plans Parameters 70 | class ListSubscriptionPlansParameters(BaseModel): 71 | product_id: Optional[str] = Field(None, description="The ID of the product for which to get subscription plans.") 72 | page: Optional[int] = Field(None, description="The page number of the result set to fetch.") 73 | page_size: Optional[int] = Field(None, description="The number of records to return per page (maximum 100).") 74 | total_required: Optional[bool] = Field(None, description="Indicates whether the response should include the total count of plans.") 75 | 76 | # Show Subscription Plan Details Parameters 77 | class ShowSubscriptionPlanDetailsParameters(BaseModel): 78 | plan_id: str = Field(..., description="The ID of the subscription plan to show.") 79 | 80 | # Name Schema 81 | class NameSchema(BaseModel): 82 | given_name: Optional[str] = Field(None, description="The subscriber given name.") 83 | surname: Optional[str] = Field(None, description="The subscriber last name.") 84 | 85 | # Address Schema 86 | class AddressSchema(BaseModel): 87 | address_line_1: str = Field(..., description="The first line of the address.") 88 | address_line_2: Optional[str] = Field(None, description="The second line of the address.") 89 | admin_area_1: str = Field(..., description="The city or locality.") 90 | admin_area_2: str = Field(..., description="The state or province.") 91 | postal_code: str = Field(..., description="The postal code.") 92 | country_code: Literal['US'] = Field(..., description="The country code.") 93 | 94 | # Shipping Address Schema 95 | class ShippingAddressSchema(BaseModel): 96 | name: Optional[NameSchema] = Field(None, description="The subscriber shipping address name.") 97 | address: Optional[AddressSchema] = Field(None, description="The subscriber shipping address.") 98 | 99 | # Payment Method Schema 100 | class PaymentMethodSchema(BaseModel): 101 | payer_selected: Literal['PAYPAL', 'CREDIT_CARD'] = Field(..., description="The payment method selected by the payer.") 102 | payee_preferred: Optional[Literal['IMMEDIATE_PAYMENT_REQUIRED', 'INSTANT_FUNDING_SOURCE']] = Field(None, description="The preferred payment method for the payee.") 103 | 104 | # Shipping Amount Schema 105 | class ShippingAmountSchema(BaseModel): 106 | currency_code: Literal['USD'] = Field(..., description="The currency code for the shipping amount.") 107 | value: str = Field(..., description="The value of the shipping amount.") 108 | 109 | # Subscriber Schema 110 | class SubscriberSchema(BaseModel): 111 | name: Optional[NameSchema] = Field(None, description="The subscriber name.") 112 | email_address: Optional[str] = Field(None, description="The subscriber email address.") 113 | shipping_address: Optional[ShippingAddressSchema] = Field(None, description="The subscriber shipping address.") 114 | 115 | # Application Context Schema 116 | class ApplicationContextSchema(BaseModel): 117 | brand_name: str = Field(..., description="The brand name.") 118 | locale: Optional[str] = Field(None, description="The locale for the subscription.") 119 | shipping_preference: Optional[Literal['SET_PROVIDED_ADDRESS', 'GET_FROM_FILE']] = Field(None, description="The shipping preference.") 120 | user_action: Optional[Literal['SUBSCRIBE_NOW', 'CONTINUE']] = Field(None, description="The user action.") 121 | return_url: str = Field(..., description="The return URL after the subscription is created.") 122 | cancel_url: str = Field(..., description="The cancel URL if the user cancels the subscription.") 123 | payment_method: Optional[PaymentMethodSchema] = Field(None, description="The payment method details.") 124 | 125 | # Create Subscription Parameters 126 | class CreateSubscriptionParameters(BaseModel): 127 | plan_id: str = Field(..., description="The ID of the subscription plan to create.") 128 | quantity: Optional[int] = Field(None, description="The quantity of the product in the subscription.") 129 | shipping_amount: Optional[ShippingAmountSchema] = Field(None, description="The shipping amount for the subscription.") 130 | subscriber: Optional[SubscriberSchema] = Field(None, description="The subscriber details.") 131 | application_context: Optional[ApplicationContextSchema] = Field(None, description="The application context for the subscription.") 132 | 133 | # Show Subscription Details Parameters 134 | class ShowSubscriptionDetailsParameters(BaseModel): 135 | subscription_id: str = Field(..., description="The ID of the subscription to show details.") 136 | 137 | class Reason(BaseModel): 138 | reason: str = Field(..., description="Reason for Cancellation.") 139 | 140 | # Cancel Subscription Parameters 141 | class CancelSubscriptionParameters(BaseModel): 142 | subscription_id: str = Field(..., description="The ID of the subscription to cancel.") 143 | payload: Reason = Field(..., description="Reason for cancellation.") 144 | 145 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/subscriptions/prompts.py: -------------------------------------------------------------------------------- 1 | 2 | CREATE_PRODUCT_PROMPT = """ 3 | Create a product in PayPal using product catalog - create products API. 4 | This function creates a new product that will be used in subscription plans, subscriptions. 5 | Required Arguments are: name (product name), type (product type). 6 | Paramters: 7 | - id: (auto-generated or specify SKU of the product) The ID of the product 8 | - name: {product_name} (required) 9 | - description: {product_description} (optional) 10 | - type {DIGITAL | PHYSICAL | SERVICE} (required) 11 | - category: {product_category} (optional) 12 | - image_url: {image_url} (optional) 13 | - home_url: {home_url} (optional) 14 | 15 | """ 16 | 17 | LIST_PRODUCTS_PROMPT = """ 18 | List products from PayPal. 19 | 20 | This function retrieves a list of products with optional pagination parameters. 21 | """ 22 | 23 | SHOW_PRODUCT_DETAILS_PROMPT = """ 24 | List products from PayPal. 25 | 26 | This function retrieves a list of products with optional pagination parameters. 27 | """ 28 | 29 | CREATE_SUBSCRIPTION_PLAN_PROMPT = """ 30 | Create a subsctiption plan in PayPal using subscription - create plan API. 31 | This function creates a new subscription plan that defines pricing and billing cycle details for subscriptions. 32 | Required parameters are: product_id (the ID of the product for which to create the plan), name (subscription plan name), billing_cycles (billing cycle details). 33 | High level: product_id, name, description, taxes, status: {CREATED|INACTIVE|ACTIVE}, billing_cycles, payment_preferences are required in json object. 34 | While creating billing_cycles object, trial(second) billing cycle should precede regular billing cycle. 35 | """ 36 | 37 | LIST_SUBSCRIPTION_PLANS_PROMPT = """ 38 | List subscription plans from PayPal. 39 | 40 | This function retrieves a list of subscription plans with optional product filtering and pagination parameters. 41 | """ 42 | 43 | SHOW_SUBSCRIPTION_PLAN_DETAILS_PROMPT = """ 44 | Show subscription plan details from PayPal. 45 | This function retrieves the details of a specific subscription plan using its ID. 46 | Required parameters are: plan_id (the ID of the subscription plan). 47 | """ 48 | 49 | 50 | CREATE_SUBSCRIPTION_PROMPT = """ 51 | Create a subscription in PayPal using the subscription - create subscription API. 52 | This function allows you to create a new subscription for a specific plan, enabling the management of recurring payments. 53 | The only required parameter is plan_id (the ID of the subscription plan). All other fields are optional and can be omitted if not provided. 54 | The subscriber field is optional. If no subscriber information is provided, omit the subscriber field in the request payload. 55 | The shipping address is optional. If no shipping address is provided, set the shipping_preference to GET_FROM_FILE in the application context. 56 | The application context is also optional. If no application context information is provided, omit the application context field in the request payload. 57 | """ 58 | 59 | SHOW_SUBSCRIPTION_DETAILS_PROMPT = """ 60 | Show subscription details from PayPal. 61 | This function retrieves the details of a specific subscription using its ID. 62 | Required parameters are: subscription_id (the ID of the subscription). 63 | """ 64 | 65 | 66 | CANCEL_SUBSCRIPTION_PROMPT = """ 67 | Cancel a customer subscription in PayPal. 68 | 69 | This function cancels an active subscription for a customer. It requires the subscription ID and an optional reason for cancellation. 70 | Required parameters are: subscription_id (the ID of the subscription to be canceled). 71 | Below is the payload request structure: 72 | { 73 | "reason": "Customer requested cancellation" 74 | } 75 | You MUST ask the user for: 76 | - subscription_id 77 | - reason for cancellation. 78 | 79 | Return all of the above as structured JSON in your response. 80 | """ -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/subscriptions/tool_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | from .parameters import * 3 | import json 4 | 5 | 6 | def create_product(client, params: dict): 7 | 8 | validated = CreateProductParameters(**params) 9 | product_uri = "/v1/catalogs/products" 10 | result = client.post(uri = product_uri, payload = validated.model_dump()) 11 | return json.dumps(result) 12 | 13 | 14 | def list_products(client, params: dict): 15 | 16 | validated = ListProductsParameters(**params) 17 | product_uri = f"/v1/catalogs/products?page_size={validated.page_size or 10}&page={validated.page or 1}&total_required={validated.total_required or 'true'}" 18 | result = client.get(uri = product_uri) 19 | return json.dumps(result) 20 | 21 | 22 | def show_product_details(client, params: dict): 23 | 24 | validated = ShowProductDetailsParameters(**params) 25 | product_uri = f"/v1/catalogs/products/{validated.product_id}" 26 | result = client.get(uri = product_uri) 27 | return json.dumps(result) 28 | 29 | 30 | def create_subscription_plan(client, params: dict): 31 | 32 | validated = CreateSubscriptionPlanParameters(**params) 33 | subscription_plan_uri = "/v1/billing/plans" 34 | result = client.post(uri = subscription_plan_uri, payload = validated.model_dump()) 35 | return json.dumps(result) 36 | 37 | 38 | def list_subscription_plans(client, params: dict): 39 | 40 | validated = ListSubscriptionPlansParameters(**params) 41 | subscription_plan_uri = f"/v1/billing/plans?page_size={validated.page_size or 10}&page={validated.page or 1}&total_required={validated.total_required or True}" 42 | if validated.product_id: 43 | subscription_plan_uri += f"&product_id={validated.product_id}" 44 | result = client.get(uri = subscription_plan_uri) 45 | return json.dumps(result) 46 | 47 | 48 | def show_subscription_plan_details(client, params: dict): 49 | 50 | validated = ShowSubscriptionPlanDetailsParameters(**params) 51 | subscription_plan_uri = f"/v1/billing/plans/{validated.plan_id}" 52 | result = client.get(uri = subscription_plan_uri) 53 | return json.dumps(result) 54 | 55 | 56 | def create_subscription(client, params: dict): 57 | 58 | validated = CreateSubscriptionParameters(**params) 59 | subscription_plan_uri = "/v1/billing/subscriptions" 60 | result = client.post(uri = subscription_plan_uri, payload = validated.model_dump()) 61 | return json.dumps(result) 62 | 63 | 64 | def show_subscription_details(client, params: dict): 65 | 66 | validated = ShowSubscriptionDetailsParameters(**params) 67 | subscription_plan_uri = f"/v1/billing/subscriptions/{validated.subscription_id}" 68 | result = client.get(uri = subscription_plan_uri) 69 | return json.dumps(result) 70 | 71 | 72 | def cancel_subscription(client, params: dict): 73 | 74 | validated = CancelSubscriptionParameters(**params) 75 | subscription_plan_uri = f"/v1/billing/subscriptions/{validated.subscription_id}/cancel" 76 | result = client.post(uri = subscription_plan_uri, payload = validated.payload.model_dump()) 77 | if not result: 78 | return "Successfully cancelled the subscription." 79 | return json.dumps(result) -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/telemetry.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import platform 3 | import sys 4 | from importlib.metadata import version, PackageNotFoundError 5 | 6 | 7 | class Telemetry: 8 | SDK_NAME = "Paypal Agent Toolkit Python" 9 | PACKAGE_NAME = "paypal-agent-toolkit" 10 | 11 | @classmethod 12 | def get_sdk_version(cls) -> str: 13 | try: 14 | return version(cls.PACKAGE_NAME) 15 | except PackageNotFoundError: 16 | return "unknown" 17 | 18 | @classmethod 19 | def get_env_info(cls) -> dict: 20 | return { 21 | "os": platform.system(), 22 | "os_version": platform.version(), 23 | "python_version": sys.version.split()[0], 24 | "platform": platform.platform(), 25 | "machine": platform.machine(), 26 | "hostname": platform.node() 27 | } 28 | 29 | 30 | @classmethod 31 | def generate_user_agent(cls, source: str) -> str: 32 | components = [ 33 | f"{cls.SDK_NAME}: {source}", 34 | f"Version: {cls.get_sdk_version()}", 35 | f"on OS: {platform.system()} {platform.release()}" 36 | ] 37 | return ", ".join(filter(None, components)) 38 | 39 | 40 | @classmethod 41 | def log(cls): 42 | telemetry_data = { 43 | "sdk_name": cls.SDK_NAME, 44 | "sdk_version": cls.get_sdk_version(), 45 | **cls.get_env_info() 46 | } 47 | logging.debug("Telemetry Info:", telemetry_data) 48 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/tracking/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional, Literal 3 | 4 | 5 | class CreateShipmentParameters(BaseModel): 6 | order_id: Optional[str] = Field( 7 | default=None, 8 | description="The ID of the order for which to create a shipment" 9 | ) 10 | tracking_number: str = Field( 11 | ..., 12 | description="The tracking number for the shipment. Id is provided by the shipper. This is required to create a shipment." 13 | ) 14 | transaction_id: str = Field( 15 | ..., 16 | description="The transaction ID associated with the shipment. Transaction id available after the order is paid or captured. This is required to create a shipment." 17 | ) 18 | status: Optional[str] = Field( 19 | default="SHIPPED", 20 | description='The status of the shipment. It can be "ON_HOLD", "SHIPPED", "DELIVERED", or "CANCELLED".' 21 | ) 22 | carrier: Optional[str] = Field( 23 | default=None, 24 | description="The carrier handling the shipment." 25 | ) 26 | 27 | 28 | class GetShipmentTrackingParameters(BaseModel): 29 | order_id: Optional[str] = Field( 30 | default=None, 31 | description="The ID of the order for which to create a shipment." 32 | ) 33 | transaction_id: Optional[str] = Field( 34 | default=None, 35 | description="The transaction ID associated with the shipment tracking to retrieve." 36 | ) 37 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/tracking/prompts.py: -------------------------------------------------------------------------------- 1 | CREATE_SHIPMENT_PROMPT = """ 2 | Create a shipment for a transaction in PayPal. 3 | This function creates a shipment record for a specific transaction, allowing you to track the shipment status and details. 4 | The transaction_id can fetch from the captured payment details in the order information. 5 | Required parameters are: tracking_number (the tracking number for the shipment), transaction_id (the transaction ID associated with the shipment). 6 | High level: tracking_number, transaction_id, status (optional), carrier (optional) are required json objects. 7 | Below is the payload request structure: 8 | { 9 | "tracking_number": "1234567890", 10 | "transaction_id": "9XJ12345ABC67890", 11 | "status": "SHIPPED", // Required: ON_HOLD, SHIPPED, DELIVERED, CANCELLED 12 | "carrier": "UPS" // Required: The carrier handling the shipment. Link to supported carriers: http://developer.paypal.com/docs/tracking/reference/carriers/ 13 | } 14 | """ 15 | 16 | GET_SHIPMENT_TRACKING_PROMPT = """ 17 | Get tracking information for a shipment by ID. 18 | This function retrieves tracking information for a specific shipment using the transaction ID and tracking number. 19 | The transaction_id can fetch from the captured payment details in the order information. 20 | Below is the payload request structure: 21 | """ -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/tracking/tool_handlers.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from typing import Dict, Any 4 | from .parameters import CreateShipmentParameters, GetShipmentTrackingParameters 5 | 6 | 7 | def create_shipment_tracking(client, params: dict) -> Dict[str, Any]: 8 | """ 9 | Create a shipment tracking entry. 10 | """ 11 | validated = CreateShipmentParameters(**params) 12 | uri = "/v1/shipping/trackers-batch" 13 | 14 | # Prepare trackers data - wrapping single shipment in an array 15 | trackers_data = { 16 | "trackers": [{ 17 | "tracking_number": validated.tracking_number, 18 | "transaction_id": validated.transaction_id, 19 | "status": validated.status, 20 | "carrier": validated.carrier 21 | }] 22 | } 23 | response = client.post(uri=uri, payload=trackers_data) 24 | return json.dumps(response) 25 | 26 | 27 | 28 | def get_shipment_tracking(client, params: dict) -> Dict[str, Any]: 29 | """ 30 | Retrieve shipment tracking information. 31 | """ 32 | validated = GetShipmentTrackingParameters(**params) 33 | transaction_id = validated.transaction_id 34 | 35 | # Check if order_id is provided and transaction_id is not 36 | if validated.order_id and not transaction_id: 37 | try: 38 | order_details = client.get_order_details(order_id=validated.order_id) 39 | 40 | if order_details and "purchase_units" in order_details and len(order_details["purchase_units"]) > 0: 41 | purchase_unit = order_details["purchase_units"][0] 42 | 43 | if "payments" in purchase_unit and "captures" in purchase_unit["payments"] and len(purchase_unit["payments"]["captures"]) > 0: 44 | capture_details = purchase_unit["payments"]["captures"][0] 45 | transaction_id = capture_details["id"] 46 | else: 47 | raise ValueError("Could not find capture id in the purchase unit details.") 48 | else: 49 | raise ValueError("Could not find purchase unit details in order details.") 50 | except Exception as error: 51 | raise ValueError(f"Error extracting transaction_id from order details: {str(error)}") 52 | 53 | if not transaction_id: 54 | raise ValueError("Either transaction_id or order_id must be provided.") 55 | 56 | uri = f"/v1/shipping/trackers?transaction_id={transaction_id}" 57 | response = client.get(uri=uri) 58 | return json.dumps(response) 59 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/transactions/parameters.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional, Literal 3 | from datetime import datetime, timedelta 4 | 5 | 6 | def default_start_date() -> str: 7 | return (datetime.utcnow() - timedelta(days=31)).isoformat(timespec="seconds") 8 | 9 | 10 | def default_end_date() -> str: 11 | return datetime.utcnow().isoformat(timespec="seconds") 12 | 13 | 14 | class ListTransactionsParameters(BaseModel): 15 | transaction_id: Optional[str] = Field( 16 | default=None, 17 | description="The ID of the transaction to retrieve." 18 | ) 19 | transaction_status: Optional[Literal["D", "P", "S", "V"]] = Field( 20 | default="S", 21 | description="Transaction status: D, P, S, or V" 22 | ) 23 | start_date: Optional[str] = Field( 24 | default_factory=default_start_date, 25 | description="Filters the transactions in the response by a start date and time, in ISO8601 format. Seconds required; fractional seconds optional." 26 | ) 27 | end_date: Optional[str] = Field( 28 | default_factory=default_end_date, 29 | description="Filters the transactions in the response by an end date and time, in ISO8601 format. Maximum range is 31 days." 30 | ) 31 | search_months: Optional[int] = Field( 32 | default=12, 33 | description="Number of months to search back for a transaction by ID. Default is 12 months." 34 | ) 35 | page_size: Optional[int] = Field(default=100) 36 | page: Optional[int] = Field(default=1) 37 | -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/transactions/prompt.py: -------------------------------------------------------------------------------- 1 | LIST_TRANSACTIONS_PROMPT = """ 2 | List transactions from PayPal. 3 | 4 | This tool is used to list transactions with optional filtering parameters within a date range of 31 days. This tool can also be used to list details of a transaction given the transaction ID. 5 | 6 | - The start_date and end_date should be specified in ISO8601 date and time format. Example dates: 1996-12-19T16:39:57-08:00, 1985-04-12T23:20:50.52Z, 1990-12-31T23:59:60Z 7 | - The transaction_status accepts the following 4 values: 8 | 1. "D" - represents denied transactions. 9 | 2. "P" - represents pending transactions. 10 | 3. "S" - represents successful transactions. 11 | 4. "V" - represents transactions that were reversed. 12 | - The transaction_id is the unique identifier for the transaction. 13 | """ -------------------------------------------------------------------------------- /python/paypal_agent_toolkit/shared/transactions/tool_handlers.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime, timedelta 3 | from typing import Dict, Any 4 | from urllib.parse import urlencode 5 | from .parameters import ListTransactionsParameters 6 | 7 | 8 | 9 | def list_transactions(client, params: dict) -> Dict[str, Any]: 10 | """ 11 | List transactions or search for a specific transaction by ID. 12 | """ 13 | validated = ListTransactionsParameters(**params) 14 | 15 | # If searching for a specific transaction by ID 16 | if validated.transaction_id: 17 | search_months = validated.search_months or 12 18 | end_date = datetime.utcnow() 19 | start_date = end_date - timedelta(days=31) 20 | 21 | for month in range(search_months): 22 | query_params = validated.dict(exclude={"search_months"}) 23 | query_params["end_date"] = end_date.isoformat() + "Z" 24 | query_params["start_date"] = start_date.isoformat() + "Z" 25 | 26 | uri = f"/v1/reporting/transactions" 27 | try: 28 | response = client.get(uri=uri, payload=query_params) 29 | response_data = json.loads(response) 30 | 31 | if response_data.get("transaction_details"): 32 | for transaction in response_data["transaction_details"]: 33 | if transaction["transaction_info"]["transaction_id"] == validated.transaction_id: 34 | return { 35 | "found": True, 36 | "transaction_details": [transaction], 37 | "total_items": 1 38 | } 39 | 40 | # Move back one month for the next search 41 | end_date = start_date 42 | start_date = start_date - timedelta(days=31) 43 | 44 | except Exception as error: 45 | # Log and continue to the next month 46 | print(f"Error searching transactions for month {month + 1}: {str(error)}") 47 | 48 | # If transaction not found after searching all months 49 | return { 50 | "found": False, 51 | "transaction_details": [], 52 | "total_items": 0, 53 | "message": f"The transaction ID {validated.transaction_id} was not found in the last {search_months} months." 54 | } 55 | 56 | else: 57 | # Listing transactions without a specific ID 58 | query_params = validated.dict(exclude={"search_months"}) 59 | 60 | if not query_params.get("end_date") and not query_params.get("start_date"): 61 | query_params["end_date"] = datetime.utcnow().isoformat() + "Z" 62 | query_params["start_date"] = (datetime.utcnow() - timedelta(days=31)).isoformat() + "Z" 63 | elif not query_params.get("end_date"): 64 | start_date = datetime.fromisoformat(query_params["start_date"].replace("Z", "")) 65 | query_params["end_date"] = (start_date + timedelta(days=31)).isoformat() + "Z" 66 | elif not query_params.get("start_date"): 67 | end_date = datetime.fromisoformat(query_params["end_date"].replace("Z", "")) 68 | query_params["start_date"] = (end_date - timedelta(days=31)).isoformat() + "Z" 69 | else: 70 | start_date = datetime.fromisoformat(query_params["start_date"].replace("Z", "")) 71 | end_date = datetime.fromisoformat(query_params["end_date"].replace("Z", "")) 72 | day_range = (end_date - start_date).days 73 | 74 | if day_range > 31: 75 | query_params["start_date"] = (end_date - timedelta(days=31)).isoformat() + "Z" 76 | 77 | query_string = urlencode(query_params) 78 | uri = f"/v1/reporting/transactions?" + query_string 79 | 80 | response = client.get(uri=uri) 81 | return json.dumps(response) 82 | 83 | 84 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "paypal-agent-toolkit" 3 | version = "1.3.0" 4 | description = "A toolkit for agent interactions with PayPal API." 5 | authors = [ 6 | { name = "PayPal", email = "support@paypal.com" } 7 | ] 8 | license = {file = "LICENSE"} 9 | keywords = ["paypal", "payments", "checkout","api"] 10 | readme = "README.md" 11 | requires-python = ">=3.11" 12 | dependencies = [ 13 | "openai-agents==0.0.2", 14 | "langchain==0.3.23", 15 | "crewai-tools==0.13.2", 16 | "httpx>=0.26.0", 17 | "requests>=2.31.0", 18 | "python-dotenv>=1.0.1", 19 | "pydantic>=2.10" 20 | ] 21 | 22 | # Build system requirements 23 | [build-system] 24 | requires = ["setuptools>=61.0", "build", "twine"] 25 | build-backend = "setuptools.build_meta" 26 | 27 | [tool.setuptools.packages.find] 28 | include = ["paypal_agent_toolkit*"] 29 | exclude = ["tests*", "examples*"] 30 | 31 | [project.urls] 32 | "Bug Tracker" = "https://github.com/paypal/agent-toolkit/issues" 33 | "Source Code" = "https://github.com/paypal/agent-toolkit" 34 | 35 | [tool.pyright] 36 | include = [ 37 | "*", 38 | ] 39 | exclude = ["build", "**/__pycache__"] 40 | reportMissingTypeArgument = true 41 | reportUnnecessaryCast = true 42 | reportUnnecessaryComparison = true 43 | reportUnnecessaryContains = true 44 | reportUnnecessaryIsInstance = true 45 | reportPrivateImportUsage = true 46 | reportUnnecessaryTypeIgnoreComment = true -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | # Core dependencies 2 | httpx>=0.26.0 3 | requests>=2.31.0 4 | python-dotenv>=1.0.1 5 | pydantic>=2.10 6 | pytest>=7.4.2 7 | 8 | #Build Tools 9 | build 10 | twine 11 | 12 | 13 | # OpenAI 14 | openai==1.66.0 15 | openai-agents==0.0.2 16 | 17 | # LangChain 18 | langchain==0.3.23 19 | langchain-openai==0.2.2 20 | 21 | # CrewAI 22 | setuptools>=61.0 23 | crewai==0.76.2 24 | crewai-tools==0.13.2 25 | -------------------------------------------------------------------------------- /typescript/.env.sample: -------------------------------------------------------------------------------- 1 | ;; 2 | ; Rename this file to .env and add the correct values for the credentials. 3 | ;; 4 | 5 | ; The PayPal Client ID and Client Secret in your PayPal Developer Dashboard. These credentials are necessary to authenticate API calls and integrate PayPal services. 6 | PAYPAL_CLIENT_ID= 7 | PAYPAL_CLIENT_SECRET= 8 | 9 | ; The PayPal environment to use. Accepted values: 'Sandbox' or 'Production' 10 | PAYPAL_ENVIRONMENT=Sandbox 11 | PAYPAL_LOG_REQUESTS=true 12 | PAYPAL_LOG_RESPONSES=true 13 | -------------------------------------------------------------------------------- /typescript/examples/ai-sdk/README.md: -------------------------------------------------------------------------------- 1 | # AI SDK Example 2 | 3 | ## Setup 4 | 5 | Populate the following variables in the local environment or in the `.env` 6 | 7 | ``` 8 | OPENAI_API_KEY=YOUR_OPENAI_API_KEY 9 | PAYPAL_CLIENT_ID=PayPal_Client_ID 10 | PAYPAL_CLIENT_SECRET=PayPal_Client_Secret 11 | ``` 12 | 13 | ## Usage 14 | 15 | ``` 16 | npx ts-node index.ts --env 17 | ``` 18 | -------------------------------------------------------------------------------- /typescript/examples/ai-sdk/index.ts: -------------------------------------------------------------------------------- 1 | import {config} from '@dotenvx/dotenvx'; 2 | import {openai} from '@ai-sdk/openai'; 3 | import {generateText} from 'ai'; 4 | import {PayPalWorkflows, PayPalAgentToolkit, ALL_TOOLS_ENABLED} from '@paypal/agent-toolkit/ai-sdk'; 5 | 6 | // Get the env file path from an environment variable, with a default fallback 7 | const envFilePath = process.env.ENV_FILE_PATH || '.env'; 8 | config({path: envFilePath}); 9 | 10 | /* 11 | * This holds all the configuration required for starting PayPal Agent Toolkit 12 | */ 13 | 14 | const ppConfig = { 15 | clientId: process.env.PAYPAL_CLIENT_ID || '', 16 | clientSecret: process.env.PAYPAL_CLIENT_SECRET || '', 17 | configuration: { 18 | actions: ALL_TOOLS_ENABLED 19 | } 20 | } 21 | /* 22 | * This holds all the tools that use PayPal functionality 23 | */ 24 | const paypalToolkit = new PayPalAgentToolkit(ppConfig); 25 | /* 26 | * This holds all the preconfigured common PayPal workflows 27 | */ 28 | const paypalWorkflows = new PayPalWorkflows(ppConfig) 29 | 30 | /* 31 | * This is the merchant's typical use case. This stays the same for most requests. 32 | */ 33 | const systemPrompt = `I am a plumber running a small business. I charge $120 per hour plus 50% tax. I use standard parts which typically include a new faucet costing between $50-80 and pipes for about $3 per foot. There is 12% tax for parts. My return URL is: http://localhost:3000/thank-you.`; 34 | 35 | 36 | // User can bring their own llm 37 | const llm = openai('gpt-4o'); 38 | 39 | (async () => { 40 | 41 | // The userPrompt is a specific prompt for a single transaction for the business. 42 | const userPrompt = `Customer needed 3 hours of work and had a standard list of parts for replacing a kitchen faucet. Create an order.` 43 | 44 | // Invoke preconfigured workflows that will orchestrate across multiple calls. 45 | const orderSummary = await paypalWorkflows.generateOrder(llm, userPrompt, systemPrompt); 46 | console.log(orderSummary) 47 | 48 | // (or) Invoke through toolkit for specific use-cases 49 | const {text: orderDetails} = await generateText({ 50 | model: openai('gpt-4o'), 51 | tools: paypalToolkit.getTools(), 52 | maxSteps: 10, 53 | prompt: "Retrieve the details of the order with ID: 4A572180UY881681N", 54 | }); 55 | console.log(orderDetails) 56 | })(); 57 | -------------------------------------------------------------------------------- /typescript/examples/ai-sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paypal-examples-ai-sdk", 3 | "version": "0.1.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "dependencies": { 9 | "@ai-sdk/openai": "^0.0.63", 10 | "@dotenvx/dotenvx": "^1.32.0", 11 | "@paypal/agent-toolkit": "latest", 12 | "ai": "^4.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^22.7.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /typescript/jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | }; 5 | -------------------------------------------------------------------------------- /typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paypal/agent-toolkit", 3 | "version": "1.3.4", 4 | "description": "PayPal toolkit for AI agent workflows in typescript", 5 | "scripts": { 6 | "clean": "rm -rf dist ai-sdk mcp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npm i && tsup", 9 | "build:debug": "TSUP_DEBUG=true npm run build" 10 | }, 11 | "exports": { 12 | "./ai-sdk": { 13 | "types": "./ai-sdk/index.d.ts", 14 | "default": "./ai-sdk/index.js" 15 | }, 16 | "./mcp": { 17 | "types": "./mcp/index.d.ts", 18 | "default": "./mcp/index.js" 19 | } 20 | }, 21 | "author": "", 22 | "license": "ISC", 23 | "files": [ 24 | "ai-sdk", 25 | "mcp", 26 | "package.json" 27 | ], 28 | "devDependencies": { 29 | "@types/debug": "^4.1.12", 30 | "@types/jest": "^29.5.14", 31 | "@types/lodash": "^4.17.15", 32 | "@types/node": "^22.10.5", 33 | "jest": "^29.7.0", 34 | "ts-jest": "^29.2.5", 35 | "ts-node": "^10.9.2", 36 | "tsup": "^8.3.6", 37 | "tsx": "^4.19.2", 38 | "typescript": "^5.7.2" 39 | }, 40 | "dependencies": { 41 | "@dotenvx/dotenvx": "^1.32.0", 42 | "@modelcontextprotocol/sdk": "^1.6.1", 43 | "@paypal/paypal-server-sdk": "^1.0.0", 44 | "ai": "^4.0.23", 45 | "axios": "^1.8.4", 46 | "debug": "^4.4.0", 47 | "fast-json-patch": "^3.1.1", 48 | "lodash": "^4.17.21", 49 | "mathjs": "^14.0.1", 50 | "os": "^0.1.2", 51 | "zod": "^3.24.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /typescript/src/ai-sdk/index.ts: -------------------------------------------------------------------------------- 1 | import PayPalAgentToolkit from "./toolkit"; 2 | import PayPalWorkflows from "./workflows"; 3 | import { ALL_TOOLS_ENABLED } from "../shared/tools"; 4 | 5 | export { PayPalAgentToolkit, PayPalWorkflows, ALL_TOOLS_ENABLED } 6 | -------------------------------------------------------------------------------- /typescript/src/ai-sdk/toolkit.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from "ai"; 2 | import PayPalAPI from "../shared/api"; 3 | import PayPalClient from "../shared/client"; 4 | import { Configuration, isToolAllowed } from "../shared/configuration"; 5 | import tools from '../shared/tools'; 6 | import PayPalTool from './tools'; 7 | 8 | 9 | const SOURCE = 'AI-SDK'; 10 | 11 | class PayPalAgentToolkit { 12 | readonly client: PayPalClient; 13 | private _paypal: PayPalAPI; 14 | private _tools: { [key: string]: Tool }; 15 | 16 | constructor({ clientId, clientSecret, configuration }: { 17 | clientId: string, 18 | clientSecret: string, 19 | configuration: Configuration, 20 | }) { 21 | const context = configuration.context || {}; 22 | this.client = new PayPalClient({ clientId: clientId, clientSecret: clientSecret, context: { ...context, source: SOURCE } }); 23 | const filteredTools = tools(context).filter((tool) => 24 | isToolAllowed(tool, configuration) 25 | ); 26 | this._paypal = new PayPalAPI(this.client, configuration.context); 27 | this._tools = filteredTools.reduce((acc, item) => { 28 | acc[item.method] = PayPalTool(this._paypal, item.method, item.description, item.parameters); 29 | return acc; 30 | }, {} as { [key: string]: Tool }); 31 | } 32 | 33 | getTools(): { [key: string]: Tool } { 34 | return this._tools; 35 | } 36 | 37 | } 38 | 39 | export default PayPalAgentToolkit; 40 | -------------------------------------------------------------------------------- /typescript/src/ai-sdk/tools.ts: -------------------------------------------------------------------------------- 1 | import type { Tool } from 'ai'; 2 | import { tool } from 'ai'; 3 | import { z } from 'zod'; 4 | import PayPalAPI from '../shared/api'; 5 | 6 | export default function PayPalTool( 7 | payPalApi: PayPalAPI, 8 | method: string, 9 | description: string, 10 | schema: z.ZodObject 11 | ): Tool { 12 | return tool({ 13 | description: description, 14 | parameters: schema, 15 | execute: (arg: z.output) => { 16 | return payPalApi.run(method, arg); 17 | }, 18 | }); 19 | } -------------------------------------------------------------------------------- /typescript/src/ai-sdk/workflows.ts: -------------------------------------------------------------------------------- 1 | import { generateObject, generateText, LanguageModelV1 } from 'ai'; 2 | 3 | import { z } from "zod"; 4 | import { PayPalAgentToolkit } from "./"; 5 | import { createOrderParameters } from '../shared/parameters'; 6 | import { Configuration } from '../shared/configuration'; 7 | 8 | class PayPalWorkflows { 9 | readonly toolkit: PayPalAgentToolkit; 10 | log?: (message: any) => void; 11 | 12 | constructor({ clientId, clientSecret, configuration, log }: { 13 | clientId: string, 14 | clientSecret: string, 15 | configuration: Configuration, 16 | log?: (message: any) => void 17 | }) { 18 | this.log = log || console.log 19 | this.toolkit = new PayPalAgentToolkit({ 20 | clientId, 21 | clientSecret, 22 | configuration, 23 | }); 24 | } 25 | 26 | public async generateOrder(llm: LanguageModelV1, userPrompt: string, systemPrompt: string): Promise { 27 | // Step 1: Generate the order details for the transaction 28 | this.log!('Step 1: I am using the provided prompts to create a request object for PayPal.'); 29 | const { object: orderObject } = await generateObject>>({ 30 | model: llm, 31 | schema: createOrderParameters({}), 32 | system: systemPrompt, 33 | prompt: userPrompt, 34 | }); 35 | this.log!(`Response 1: I have now created the request object with provided details;\n ${JSON.stringify(orderObject)}`); 36 | this.log!(`Proceeding with next step.`) 37 | this.log!(`Step 2: I am now choosing the correct tool from PayPal's toolkit to create an an order using the generated object from previous step.`); 38 | const { text: orderId } = await generateText({ 39 | model: llm, 40 | tools: this.toolkit.getTools(), 41 | maxSteps: 10, 42 | prompt: `Create an order using the following details: ${JSON.stringify(orderObject, null, 2)}`, 43 | }); 44 | this.log!(`Response 2: I have created the order in PayPal's system with order ID: ${orderId}.`); 45 | this.log!(`Proceeding with next step.`) 46 | // Step 3: Retrieve the order details from PayPal 47 | this.log!(`Step 3: I am now choosing the correct tool from PayPal's toolkit to retrieve the order details with the order ID from previous step.`); 48 | const { text: orderDetails } = await generateText({ 49 | model: llm, 50 | tools: this.toolkit.getTools(), 51 | maxSteps: 10, 52 | system: 'You are processing an order using PayPal APIs. Use the order ID to retrieve the order details.', 53 | prompt: `Retrieve the details of the order with ID: ${orderId}.`, 54 | }); 55 | this.log!(`Response 3: Here is the order details with ID: ${orderId}; ${JSON.stringify(orderDetails, null, 2)}`); 56 | this.log!(`Proceeding with next step.`) 57 | // Step 4: Generate the summary for the order 58 | this.log!('Step 4: I am now generating the summary of the order using the order details retrieved in the previous step. This makes it easier to read the important information.'); 59 | const { text: summary } = await generateText({ 60 | model: llm, 61 | maxSteps: 10, 62 | system: 'You are tasked with generating an summary text for the order. Use the provided order details. For each item in order, update item level tax to be the tax amount*quantity of the item. Show the payment approval link only at the end of the page for the customer and hide all other links related to the order.', 63 | prompt: ` 64 | Generate a summary for the following order details: ${orderDetails}. 65 | `, 66 | }); 67 | this.log!(`Response 4: \n${summary}`); 68 | return summary; 69 | } 70 | 71 | public async getDisputeDetails(llm: LanguageModelV1, userPrompt: string): Promise { 72 | // Step 1: Retrieve the dispute details for the transaction 73 | this.log!(`Step 1. I am now retrieving the details from PayPal.`); 74 | const { text: disputeDetail } = await generateText({ 75 | model: llm, 76 | tools: this.toolkit.getTools(), 77 | maxSteps: 10, 78 | system: 'You are a tool to retrieve the dispute details based on the dispute ID.', 79 | prompt: userPrompt, 80 | }); 81 | this.log!('Step 2. I am now generating the summary of the dispute using the dispute details retrieved in the previous step. This makes it easier to read the important information.'); 82 | // Generate the summary for the dispute details 83 | const { text: summary } = await generateText({ 84 | model: llm, 85 | tools: this.toolkit.getTools(), 86 | system: 'You are tasked with generating an summary text for the dispute. Use the provided dispute details. Show the appropriate links only at the end of the page.', 87 | prompt: ` 88 | Generate a summary for the following dispute details: ${disputeDetail}. 89 | `, 90 | }); 91 | this.log!(`Output: ${summary}`); 92 | return summary; 93 | } 94 | } 95 | 96 | export default PayPalWorkflows; 97 | -------------------------------------------------------------------------------- /typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | // Export shared functionality 2 | export * from './shared/api'; 3 | export * from './shared/configuration'; 4 | export * from './shared/functions'; 5 | export * from './shared/parameters'; 6 | export * from './shared/prompts'; 7 | export * from './shared/tools'; 8 | 9 | // Re-export modelcontextprotocol 10 | import * as modelcontextprotocol from './modelcontextprotocol'; 11 | export { modelcontextprotocol }; -------------------------------------------------------------------------------- /typescript/src/modelcontextprotocol/index.ts: -------------------------------------------------------------------------------- 1 | import PayPalAgentToolkit from './toolkit'; 2 | import PayPalMCPToolkit from './mcpToolkit'; 3 | import PayPalAPI from '../shared/api'; 4 | import {Tool} from '../shared/tools'; 5 | 6 | export { 7 | PayPalAgentToolkit, 8 | PayPalMCPToolkit, 9 | PayPalAPI, 10 | Tool 11 | }; 12 | -------------------------------------------------------------------------------- /typescript/src/modelcontextprotocol/mcpToolkit.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { Configuration, isToolAllowed } from '../shared/configuration'; 4 | import PayPalAPI from '../shared/api'; 5 | import tools, {Tool} from '../shared/tools'; 6 | 7 | const SOURCE = 'Remote MCP'; 8 | 9 | class PayPalMCPToolkit { 10 | private _paypal: PayPalAPI; 11 | private readonly filteredTools: Tool[] = []; 12 | 13 | constructor({ 14 | accessToken, 15 | configuration, 16 | }: { 17 | accessToken: string; 18 | configuration: Configuration; 19 | }) { 20 | 21 | this._paypal = new PayPalAPI(accessToken, { ...configuration.context, source: SOURCE }); 22 | const context = configuration.context || {}; 23 | this.filteredTools = tools(context).filter((tool) => 24 | isToolAllowed(tool, configuration) 25 | ); 26 | } 27 | 28 | public getTools(): Tool[] { 29 | return this.filteredTools; 30 | } 31 | 32 | public getPaypalAPIService(): PayPalAPI { 33 | return this._paypal; 34 | } 35 | } 36 | 37 | export default PayPalMCPToolkit; 38 | 39 | -------------------------------------------------------------------------------- /typescript/src/modelcontextprotocol/toolkit.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; 2 | import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; 3 | import { Configuration, isToolAllowed } from '../shared/configuration'; 4 | import PayPalAPI from '../shared/api'; 5 | import tools from '../shared/tools'; 6 | import { version } from '../../package.json'; 7 | 8 | 9 | const SOURCE = 'MCP'; 10 | 11 | class PayPalAgentToolkit extends McpServer { 12 | private _paypal: PayPalAPI; 13 | 14 | constructor({ 15 | accessToken, 16 | configuration, 17 | }: { 18 | accessToken: string; 19 | configuration: Configuration; 20 | }) { 21 | super({ 22 | name: 'PayPal', 23 | version: version, 24 | }); 25 | 26 | this._paypal = new PayPalAPI(accessToken, { ...configuration.context, source: SOURCE }); 27 | 28 | const context = configuration.context || {}; 29 | const filteredTools = tools(context).filter((tool) => 30 | isToolAllowed(tool, configuration) 31 | ); 32 | 33 | filteredTools.forEach((tool) => { 34 | const regTool = this.tool( 35 | tool.method, 36 | tool.description, 37 | tool.parameters.shape, 38 | async (arg: any, _extra: RequestHandlerExtra) => { 39 | const result = await this._paypal.run(tool.method, arg); 40 | return { 41 | content: [ 42 | { 43 | type: 'text' as const, 44 | text: String(result), 45 | }, 46 | ], 47 | }; 48 | } 49 | ); 50 | }); 51 | } 52 | } 53 | 54 | export default PayPalAgentToolkit; 55 | -------------------------------------------------------------------------------- /typescript/src/shared/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createInvoice, 3 | listInvoices, 4 | getInvoice, 5 | sendInvoice, 6 | sendInvoiceReminder, 7 | cancelSentInvoice, 8 | createProduct, 9 | listProducts, 10 | createSubscriptionPlan, 11 | listSubscriptionPlans, 12 | createShipment, 13 | getShipmentTracking, 14 | generateInvoiceQrCode, 15 | createOrder, 16 | getOrder, 17 | listDisputes, 18 | getDispute, 19 | acceptDisputeClaim, 20 | captureOrder, listTransactions, 21 | createSubscription, 22 | showProductDetails, 23 | showSubscriptionPlanDetails, 24 | showSubscriptionDetails, 25 | cancelSubscription, 26 | } from './functions'; 27 | 28 | import type { Context } from './configuration'; 29 | import PayPalClient from './client'; 30 | 31 | class PayPalAPI { 32 | paypalClient: PayPalClient; 33 | context: Context; 34 | baseUrl: string; 35 | accessToken?: string; 36 | 37 | constructor(paypalClientOrAccessToken: PayPalClient | string, context?: Context) { 38 | this.context = context || {}; 39 | 40 | // Set default sandbox mode if not provided 41 | this.context.sandbox = this.context.sandbox ?? false; 42 | this.baseUrl = this.context.sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com'; 43 | 44 | if (typeof paypalClientOrAccessToken === 'string') { 45 | this.accessToken = paypalClientOrAccessToken; 46 | this.paypalClient = new PayPalClient({context: this.context, accessToken: this.accessToken }); 47 | } else { 48 | this.paypalClient = paypalClientOrAccessToken; 49 | } 50 | 51 | 52 | 53 | } 54 | 55 | 56 | async run(method: string, arg: any): Promise { 57 | try { 58 | const output = await this.executeMethod(method, arg); 59 | return JSON.stringify(output); 60 | } catch (error: any) { 61 | const errorMessage = error.message || 'Unknown error'; 62 | return JSON.stringify({ 63 | error: { 64 | message: errorMessage, 65 | type: 'paypal_error', 66 | }, 67 | }); 68 | } 69 | } 70 | 71 | private async executeMethod(method: string, arg: any): Promise { 72 | switch (method) { 73 | case 'create_invoice': 74 | return createInvoice(this.paypalClient, this.context, arg); 75 | case 'list_invoices': 76 | return listInvoices(this.paypalClient, this.context, arg); 77 | case 'get_invoice': 78 | return getInvoice(this.paypalClient, this.context, arg); 79 | case 'send_invoice': 80 | return sendInvoice(this.paypalClient, this.context, arg); 81 | case 'send_invoice_reminder': 82 | return sendInvoiceReminder(this.paypalClient, this.context, arg); 83 | case 'cancel_sent_invoice': 84 | return cancelSentInvoice(this.paypalClient, this.context, arg); 85 | case 'generate_invoice_qr_code': 86 | return generateInvoiceQrCode(this.paypalClient, this.context, arg); 87 | case 'create_product': 88 | return createProduct(this.paypalClient, this.context, arg); 89 | case 'list_products': 90 | return listProducts(this.paypalClient, this.context, arg); 91 | case 'show_product_details': 92 | return showProductDetails(this.paypalClient, this.context, arg); 93 | case 'create_subscription_plan': 94 | return createSubscriptionPlan(this.paypalClient, this.context, arg); 95 | case 'list_subscription_plans': 96 | return listSubscriptionPlans(this.paypalClient, this.context, arg); 97 | case 'show_subscription_plan_details': 98 | return showSubscriptionPlanDetails(this.paypalClient, this.context, arg); 99 | case 'create_subscription': 100 | return createSubscription(this.paypalClient, this.context, arg); 101 | case 'show_subscription_details': 102 | return showSubscriptionDetails(this.paypalClient, this.context, arg); 103 | case 'cancel_subscription': 104 | return cancelSubscription(this.paypalClient, this.context, arg); 105 | case 'create_shipment_tracking': 106 | return createShipment(this.paypalClient, this.context, arg); 107 | case 'get_shipment_tracking': 108 | return getShipmentTracking(this.paypalClient, this.context, arg); 109 | case 'create_order': 110 | return createOrder(this.paypalClient, this.context, arg); 111 | case 'get_order': 112 | return getOrder(this.paypalClient, this.context, arg); 113 | case 'pay_order': 114 | return captureOrder(this.paypalClient, this.context, arg); 115 | case 'list_disputes': 116 | return listDisputes(this.paypalClient, this.context, arg); 117 | case 'get_dispute': 118 | return getDispute(this.paypalClient, this.context, arg); 119 | case 'accept_dispute_claim': 120 | return acceptDisputeClaim(this.paypalClient, this.context, arg); 121 | case 'list_transactions': 122 | return listTransactions(this.paypalClient, this.context, arg); 123 | default: 124 | throw new Error(`Invalid method: ${method}`); 125 | } 126 | } 127 | } 128 | 129 | export default PayPalAPI; 130 | -------------------------------------------------------------------------------- /typescript/src/shared/client.ts: -------------------------------------------------------------------------------- 1 | import { Client, Environment, LogLevel } from '@paypal/paypal-server-sdk'; 2 | import axios from 'axios'; 3 | import { Buffer } from 'buffer'; 4 | import os from 'os'; 5 | import { version } from '../../package.json'; 6 | import { Context } from './configuration'; 7 | import debug from "debug"; 8 | 9 | 10 | const logger = debug('agent-toolkit:client'); 11 | 12 | class PayPalClient { 13 | private _sdkClient: Client | undefined; 14 | private _clientId: string | undefined; 15 | private _clientSecret: string | undefined; 16 | private _isSandbox: boolean; 17 | private _accessToken: string | undefined; 18 | private _baseUrl: string 19 | private _context: Context 20 | 21 | constructor({ clientId, clientSecret, context }: { 22 | clientId: string, 23 | clientSecret: string, 24 | context: Context 25 | }); 26 | 27 | constructor({ context, accessToken }: { 28 | context: Context, 29 | accessToken?: string 30 | }); 31 | 32 | constructor({ clientId, clientSecret, context, accessToken }: { 33 | clientId?: string, 34 | clientSecret?: string, 35 | context: Context, 36 | accessToken?: string 37 | }) { 38 | 39 | this._context = context; 40 | const debugSdk = this._context.debug ?? false; 41 | this._clientId = clientId; 42 | this._clientSecret = clientSecret; 43 | this._isSandbox = this._context?.sandbox ?? false; 44 | this._accessToken = accessToken; 45 | if (this._clientId !== undefined && this._clientSecret !== undefined) { 46 | this.createSDKClient(this._clientId, this._clientSecret, debugSdk); 47 | } 48 | 49 | this._baseUrl = this._isSandbox 50 | ? 'https://api.sandbox.paypal.com' 51 | : 'https://api.paypal.com'; 52 | 53 | logger(`[PayPal Setttings] Environment: ${this._isSandbox ? "Sandbox" : "Live"}`); 54 | logger(`[PayPal Setttings] API Base: ${this._baseUrl}`); 55 | } 56 | 57 | private createSDKClient(clientId: string, clientSecret: string, debugSdk: boolean) { 58 | 59 | this._sdkClient = new Client({ 60 | clientCredentialsAuthCredentials: { 61 | oAuthClientId: clientId, 62 | oAuthClientSecret: clientSecret 63 | }, 64 | timeout: 0, 65 | environment: this._isSandbox ? Environment.Sandbox : Environment.Production, 66 | ...(debugSdk && { 67 | logging: { 68 | logLevel: LogLevel.Info, 69 | maskSensitiveHeaders: true, 70 | logRequest: { 71 | logBody: true, 72 | }, 73 | logResponse: { 74 | logBody: true, 75 | logHeaders: true, 76 | }, 77 | } 78 | }), 79 | }); 80 | } 81 | 82 | async getAccessToken(): Promise { 83 | const auth = Buffer.from(`${this._clientId}:${this._clientSecret}`).toString('base64'); 84 | const url = this._baseUrl+'/v1/oauth2/token'; 85 | try { 86 | const response = await axios.post( 87 | url, 88 | 'grant_type=client_credentials', 89 | { 90 | headers: { 91 | 'Authorization': `Basic ${auth}`, 92 | 'Content-Type': 'application/x-www-form-urlencoded', 93 | 'User-Agent': this.generateUserAgent(), 94 | }, 95 | } 96 | ); 97 | return response.data.access_token; 98 | } catch (error: any) { 99 | if (axios.isAxiosError(error)) { 100 | throw new Error(`Failed to fetch access token: ${error.response?.data?.error_description || error.message}`); 101 | } else { 102 | throw new Error(`Failed to fetch access token: ${error instanceof Error ? error.message : String(error)}`); 103 | } 104 | } 105 | } 106 | 107 | // Helper method to get base URL 108 | getBaseUrl(): string { 109 | return this._baseUrl; 110 | } 111 | 112 | // Helper method to get headers 113 | async getHeaders(): Promise> { 114 | const headers: Record = { 115 | 'Content-Type': 'application/json', 116 | }; 117 | 118 | this._accessToken = this._accessToken || (await this.getAccessToken()); 119 | headers['Authorization'] = `Bearer ${this._accessToken}`; 120 | 121 | // Add additional headers if needed 122 | if (this._context.request_id) { 123 | headers['PayPal-Request-Id'] = this._context.request_id; 124 | } 125 | 126 | if (this._context.tenant_context) { 127 | headers['PayPal-Tenant-Context'] = JSON.stringify(this._context.tenant_context); 128 | } 129 | 130 | headers['User-Agent'] = this.generateUserAgent(); 131 | 132 | return headers; 133 | } 134 | 135 | private generateUserAgent(): string { 136 | const components = [ 137 | `PayPal Agent Toolkit Typescript: ${this._context.source}`, 138 | `Version: ${version}`, 139 | `on OS: ${os.platform()} ${os.release()}` 140 | ]; 141 | 142 | return components.filter(Boolean).join(', '); 143 | } 144 | 145 | } 146 | 147 | export default PayPalClient; 148 | -------------------------------------------------------------------------------- /typescript/src/shared/configuration.ts: -------------------------------------------------------------------------------- 1 | export type Context = { 2 | merchant_id?: string; 3 | sandbox?: boolean; 4 | access_token?: string; 5 | request_id?: string; 6 | tenant_context?: any; 7 | debug?: boolean; 8 | [key: string]: any; 9 | }; 10 | 11 | export type Configuration = { 12 | actions: { 13 | [product: string]: { 14 | [action: string]: boolean; 15 | }; 16 | }; 17 | context?: Context; 18 | }; 19 | 20 | export function isToolAllowed( 21 | tool: { actions: { [key: string]: { [action: string]: boolean } } }, 22 | configuration: Configuration 23 | ): boolean { 24 | for (const product in tool.actions) { 25 | for (const action in tool.actions[product]) { 26 | if ( 27 | configuration.actions[product] && 28 | configuration.actions[product][action] 29 | ) { 30 | return true; 31 | } 32 | } 33 | } 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /typescript/src/shared/payloadUtils.ts: -------------------------------------------------------------------------------- 1 | import { TypeOf } from "zod"; 2 | import { createOrderParameters } from "./parameters"; 3 | import { round } from "mathjs"; 4 | import { snakeCase, camelCase } from "lodash"; 5 | 6 | export function parseOrderDetails(params: TypeOf>) { 7 | try { 8 | const currCode = params.currencyCode; 9 | let items: any[] = []; 10 | const subTotal = params.items.reduce((sum, item) => sum + item.itemCost * item.quantity, 0); 11 | const shippingCost = params.shippingCost || 0; 12 | const taxAmount = params.items.reduce((sum, item) => sum + item.itemCost * item.taxPercent * item.quantity / 100, 0) 13 | const discount = params.discount || 0; 14 | const total = subTotal + taxAmount + shippingCost - discount; 15 | const amountBreakdown = { 16 | item_total: { 17 | value: round(subTotal, 2).toString(), 18 | currency_code: currCode 19 | }, 20 | shipping: { 21 | value: round(shippingCost, 2).toString(), 22 | currency_code: currCode 23 | }, 24 | tax_total: { 25 | value: round(taxAmount, 2).toString(), 26 | currency_code: currCode 27 | }, 28 | discount: { 29 | value: round(discount, 2).toString(), 30 | currency_code: currCode 31 | }, 32 | } 33 | params.items.forEach(item => { 34 | items.push({ 35 | name: item.name, 36 | description: item.description, 37 | unit_amount: { 38 | value: item.itemCost.toString() || '0', 39 | currency_code: currCode 40 | }, 41 | quantity: item.quantity.toString() || '1', 42 | tax: { 43 | value: round((item.itemCost * item.taxPercent) / 100, 2).toString() || '0', 44 | currency_code: currCode 45 | } 46 | }) 47 | }) 48 | const basePurchaseUnit = { 49 | amount: { 50 | value: round(total, 2).toString(), 51 | currency_code: currCode, 52 | breakdown: amountBreakdown 53 | }, 54 | items: items, 55 | } 56 | // Conditionally add shipping address if available 57 | const purchaseUnit = params.shippingAddress 58 | ? { ...basePurchaseUnit, shipping: { address: params.shippingAddress } } 59 | : basePurchaseUnit; 60 | const request = { 61 | intent: 'CAPTURE', 62 | purchase_units: [purchaseUnit], 63 | } 64 | if (params.returnUrl || params.cancelUrl) { 65 | // @ts-expect-error 66 | request.payment_source = { 67 | paypal: { 68 | experience_context: { 69 | return_url: params.returnUrl, 70 | cancel_url: params.cancelUrl 71 | } 72 | } 73 | } 74 | } 75 | return request; 76 | } catch (error) { 77 | console.error(error); 78 | throw new Error('Failed to parse order details'); 79 | } 80 | } 81 | 82 | export const toSnakeCaseKeys = (obj: any): any => { 83 | if (Array.isArray(obj)) { 84 | return obj.map(toSnakeCaseKeys); 85 | } else if (typeof obj === "object" && obj !== null) { 86 | return Object.fromEntries( 87 | Object.entries(obj).map(([key, value]) => [snakeCase(key), toSnakeCaseKeys(value)]) 88 | ); 89 | } 90 | return obj; 91 | }; 92 | 93 | export const toCamelCaseKeys = (obj: any): any => { 94 | if (Array.isArray(obj)) { 95 | return obj.map(toCamelCaseKeys); 96 | } else if (typeof obj === 'object' && obj !== null) { 97 | return Object.fromEntries( 98 | Object.entries(obj).map(([key, value]) => [camelCase(key), toCamelCaseKeys(value)]) 99 | ); 100 | } 101 | return obj; 102 | }; 103 | 104 | export function toQueryString(params: TypeOf>): string { 105 | return Object.entries(params) 106 | .filter(([_, value]) => value !== undefined && value !== null) 107 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) 108 | .join('&'); 109 | } -------------------------------------------------------------------------------- /typescript/src/shared/prompts.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from './configuration'; 2 | 3 | // === INVOICE PARAMETERS === 4 | 5 | export const createInvoicePrompt = (context: Context) => ` 6 | Create Invoices on PayPal. 7 | 8 | This function is used to create an invoice in the PayPal system. It allows you to generate a new invoice, specifying details such as customer information, items, quantities, pricing, and tax information. Once created, an invoice can be sent to the customer for payment. 9 | `; 10 | 11 | export const listInvoicesPrompt = (context: Context) => ` 12 | List invoices from PayPal. 13 | 14 | This function retrieves a list of invoices with optional pagination parameters. 15 | `; 16 | 17 | export const getInvoicePrompt = (context: Context) => ` 18 | Get an invoice from PayPal. 19 | 20 | This function retrieves details of a specific invoice using its ID. 21 | ` 22 | 23 | export const sendInvoicePrompt = (context: Context) => ` 24 | Send an invoice to the recipient(s). 25 | 26 | This function sends a previously created invoice to its intended recipients. 27 | `; 28 | 29 | export const sendInvoiceReminderPrompt = (context: Context) => ` 30 | Send a reminder for an invoice. 31 | 32 | This function sends a reminder for an invoice that has already been sent but hasn't been paid yet. 33 | `; 34 | 35 | export const cancelSentInvoicePrompt = (context: Context) => ` 36 | Cancel a sent invoice. 37 | 38 | This function cancels an invoice that has already been sent to the recipient(s). 39 | `; 40 | 41 | export const generateInvoiceQrCodePrompt = (context: Context) => ` 42 | Generate a QR code for an invoice. 43 | 44 | This function generates a QR code for an invoice, which can be used to pay the invoice using a mobile device or scanning app. 45 | `; 46 | 47 | export const createProductPrompt = (context: Context) => ` 48 | Create a product in PayPal using product catalog - create products API. 49 | This function creates a new product that will be used in subscription plans, subscriptions. 50 | Required parameters are: name (product name), type (product type). 51 | High level: 52 | - id: (auto-generated or specify SKU of the product) The ID of the product 53 | - name: {product_name} (required) 54 | - description: {product_description} (optional) 55 | - type {DIGITAL | PHYSICAL | SERVICE} (required) 56 | - category: {product_category} (optional) 57 | - image_url: {image_url} (optional) 58 | - home_url: {home_url} (optional) 59 | 60 | Below is the payload request structure: 61 | { 62 | "id": "#PROD-XYAB12ABSB7868434", 63 | "name": "Video Streaming Service", 64 | "description": "Service for streaming latest series, movies etc.", 65 | "type": "SERVICE", 66 | "category": "SOFTWARE", 67 | "image_url": "https://example.com/streaming.jpg", 68 | "home_url": "https://example.com/home" 69 | } 70 | 71 | `; 72 | 73 | export const listProductsPrompt = (context: Context) => ` 74 | List products from PayPal. 75 | 76 | This function retrieves a list of products with optional pagination parameters. 77 | `; 78 | 79 | export const showProductDetailsPrompt = (context: Context) => ` 80 | List products from PayPal. 81 | 82 | This function retrieves a list of products with optional pagination parameters. 83 | `; 84 | 85 | export const updateProductPrompt = (context: Context) => ` 86 | Update a product in PayPal. 87 | 88 | This function updates an existing product using JSON Patch operations. 89 | `; 90 | 91 | export const createSubscriptionPlanPrompt = (context: Context) => ` 92 | Create a subsctiption plan in PayPal using subscription - create plan API. 93 | This function creates a new subscription plan that defines pricing and billing cycle details for subscriptions. 94 | Required parameters are: product_id (the ID of the product for which to create the plan), name (subscription plan name), billing_cycles (billing cycle details). 95 | High level: product_id, name, description, taxes, status: {CREATED|INACTIVE|ACTIVE}, billing_cycles, payment_preferences are required in json object. 96 | While creating billing_cycles object, trial(second) billing cycle should precede regular billing cycle. 97 | `; 98 | 99 | export const listSubscriptionPlansPrompt = (context: Context) => ` 100 | List subscription plans from PayPal. 101 | 102 | This function retrieves a list of subscription plans with optional product filtering and pagination parameters. 103 | `; 104 | 105 | export const showSubscriptionPlanDetailsPrompt = (context: Context) => ` 106 | Show subscription plan details from PayPal. 107 | This function retrieves the details of a specific subscription plan using its ID. 108 | Required parameters are: plan_id (the ID of the subscription plan). 109 | `; 110 | 111 | export const createSubscriptionPrompt = (context: Context) => ` 112 | Create a subscription in PayPal using the subscription - create subscription API. 113 | This function allows you to create a new subscription for a specific plan, enabling the management of recurring payments. 114 | The only required parameter is plan_id (the ID of the subscription plan). All other fields are optional and can be omitted if not provided. 115 | The subscriber field is optional. If no subscriber information is provided, omit the subscriber field in the request payload. 116 | The shipping address is optional. If no shipping address is provided, set the shipping_preference to GET_FROM_FILE in the application context. 117 | The application context is also optional. If no application context information is provided, omit the application context field in the request payload. 118 | `; 119 | 120 | export const showSubscriptionDetailsPrompt = (context: Context) => ` 121 | Show subscription details from PayPal. 122 | This function retrieves the details of a specific subscription using its ID. 123 | Required parameters are: subscription_id (the ID of the subscription). 124 | `; 125 | 126 | 127 | export const cancelSubscriptionPrompt = (context: Context) => ` 128 | Cancel a customer subscription in PayPal. 129 | 130 | This function cancels an active subscription for a customer. It requires the subscription ID and an optional reason for cancellation. 131 | Required parameters are: subscription_id (the ID of the subscription to be canceled). 132 | Below is the payload request structure: 133 | { 134 | "reason": "Customer requested cancellation" 135 | } 136 | You MUST ask the user for: 137 | - subscription_id 138 | - reason for cancellation. 139 | 140 | Return all of the above as structured JSON in your response. 141 | `; 142 | 143 | 144 | export const createShipmentPrompt = (context: Context) => ` 145 | Create a shipment for a transaction in PayPal. 146 | This function creates a shipment record for a specific transaction, allowing you to track the shipment status and details. 147 | The transaction_id can fetch from the captured payment details in the order information. 148 | Required parameters are: tracking_number (the tracking number for the shipment), transaction_id (the transaction ID associated with the shipment). 149 | High level: tracking_number, transaction_id, status (optional), carrier (optional) are required json objects. 150 | Below is the payload request structure: 151 | { 152 | "tracking_number": "1234567890", 153 | "transaction_id": "9XJ12345ABC67890", 154 | "status": "SHIPPED", // Required: ON_HOLD, SHIPPED, DELIVERED, CANCELLED 155 | "carrier": "UPS" // Required: The carrier handling the shipment. Link to supported carriers: http://developer.paypal.com/docs/tracking/reference/carriers/ 156 | } 157 | `; 158 | 159 | export const getShipmentTrackingPrompt = (context: Context) => ` 160 | Get tracking information for a shipment by ID. 161 | This function retrieves tracking information for a specific shipment using the transaction ID and tracking number. 162 | The transaction_id can fetch from the captured payment details in the order information. 163 | Below is the payload request structure: 164 | `; 165 | 166 | // === ORDER PROMPTS === 167 | 168 | export const createOrderPrompt = (context: Context) => ` 169 | Create an order in PayPal. 170 | 171 | This tool is used to create a new order in PayPal. This is typically the first step in initiating a payment flow. It sets up an order with specified details such as item(s) to be purchased, quantity, amount, currency, and other details. 172 | `; 173 | 174 | export const getOrderPrompt = (context: Context) => ` 175 | Retrieves the order details from PayPal for a given order ID. 176 | 177 | This tool is used to retrieve details of an existing order in PayPal. It provides information about the order, including items, amounts, status, and other relevant details. 178 | `; 179 | 180 | export const captureOrderPrompt = (context: Context) => ` 181 | Capture a payment for an order. 182 | 183 | This tool is used to capture a payment for an order. It allows you to capture funds that have been authorized for a specific order but not yet captured. 184 | `; 185 | // === DISPUTE PROMPTS === 186 | 187 | export const listDisputesPrompt = (context: Context) => ` 188 | List disputes from PayPal. 189 | 190 | This function retrieves a list of disputes with optional pagination and filtering parameters. 191 | `; 192 | 193 | export const getDisputePrompt = (context: Context) => ` 194 | Get details for a specific dispute from PayPal. 195 | 196 | This tool is used to lists disputes with a summary set of details, which shows the dispute_id, reason, status, dispute_state, dispute_life_cycle_stage, dispute_channel, dispute_amount, create_time and update_time fields. 197 | `; 198 | 199 | export const acceptDisputeClaimPrompt = (context: Context) => ` 200 | Accept liability for a dispute claim. 201 | 202 | This tool is used to accept liability for a dispute claim. When you accept liability for a dispute claim, the dispute closes in the customer's favor and PayPal automatically refunds money to the customer from the merchant's account. 203 | ` 204 | 205 | export const listTransactionsPrompt = (context: Context) => ` 206 | List transactions from PayPal. 207 | 208 | This tool is used to list transactions with optional filtering parameters within a date range of 31 days. This tool can also be used to list details of a transaction given the transaction ID. 209 | 210 | - The start_date and end_date should be specified in ISO8601 date and time format. Example dates: 1996-12-19T16:39:57-08:00, 1985-04-12T23:20:50.52Z, 1990-12-31T23:59:60Z 211 | - The transaction_status accepts the following 4 values: 212 | 1. "D" - represents denied transactions. 213 | 2. "P" - represents pending transactions. 214 | 3. "S" - represents successful transactions. 215 | 4. "V" - represents transactions that were reversed. 216 | - The transaction_id is the unique identifier for the transaction. 217 | ` 218 | -------------------------------------------------------------------------------- /typescript/src/shared/tools.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | import { 4 | createInvoicePrompt, 5 | listInvoicesPrompt, 6 | getInvoicePrompt, 7 | sendInvoicePrompt, 8 | sendInvoiceReminderPrompt, 9 | cancelSentInvoicePrompt, 10 | createShipmentPrompt, 11 | getShipmentTrackingPrompt, 12 | generateInvoiceQrCodePrompt, 13 | createOrderPrompt, 14 | getOrderPrompt, 15 | getDisputePrompt, 16 | listDisputesPrompt, 17 | acceptDisputeClaimPrompt, 18 | captureOrderPrompt, 19 | listTransactionsPrompt, 20 | createProductPrompt, 21 | listProductsPrompt, 22 | showProductDetailsPrompt, 23 | updateProductPrompt, 24 | createSubscriptionPlanPrompt, 25 | listSubscriptionPlansPrompt, 26 | showSubscriptionPlanDetailsPrompt, 27 | createSubscriptionPrompt, 28 | showSubscriptionDetailsPrompt, 29 | cancelSubscriptionPrompt, 30 | } from './prompts'; 31 | 32 | import { 33 | createInvoiceParameters, 34 | listInvoicesParameters, 35 | getInvoicParameters, 36 | sendInvoiceParameters, 37 | sendInvoiceReminderParameters, 38 | cancelSentInvoiceParameters, 39 | createShipmentParameters, 40 | getShipmentTrackingParameters, 41 | generateInvoiceQrCodeParameters, 42 | createOrderParameters, 43 | getOrderParameters, 44 | getDisputeParameters, 45 | listDisputesParameters, 46 | acceptDisputeClaimParameters, 47 | captureOrderParameters, 48 | listTransactionsParameters, 49 | createProductParameters, 50 | listProductsParameters, 51 | updateProductParameters, 52 | showProductDetailsParameters, 53 | createSubscriptionPlanParameters, 54 | listSubscriptionPlansParameters, 55 | showSubscriptionPlanDetailsParameters, 56 | createSubscriptionParameters, 57 | showSubscriptionDetailsParameters, 58 | cancelSubscriptionParameters, 59 | } from './parameters'; 60 | 61 | import type { Context } from './configuration'; 62 | 63 | export type Tool = { 64 | method: string; 65 | name: string; 66 | description: string; 67 | parameters: z.ZodObject; 68 | actions: { 69 | [key: string]: { 70 | [action: string]: boolean; 71 | }; 72 | }; 73 | }; 74 | 75 | const tools = (context: Context): Tool[] => [ 76 | { 77 | method: 'create_invoice', 78 | name: 'Create Invoice', 79 | description: createInvoicePrompt(context), 80 | parameters: createInvoiceParameters(context), 81 | actions: { 82 | invoices: { 83 | create: true, 84 | }, 85 | }, 86 | }, 87 | { 88 | method: 'list_invoices', 89 | name: 'List Invoices', 90 | description: listInvoicesPrompt(context), 91 | parameters: listInvoicesParameters(context), 92 | actions: { 93 | invoices: { 94 | list: true, 95 | }, 96 | }, 97 | }, 98 | { 99 | method: 'get_invoice', 100 | name: 'Get Invoice', 101 | description: getInvoicePrompt(context), 102 | parameters: getInvoicParameters(context), 103 | actions: { 104 | invoices: { 105 | get: true, 106 | }, 107 | }, 108 | }, 109 | { 110 | method: 'send_invoice', 111 | name: 'Send Invoice', 112 | description: sendInvoicePrompt(context), 113 | parameters: sendInvoiceParameters(context), 114 | actions: { 115 | invoices: { 116 | send: true, 117 | }, 118 | }, 119 | }, 120 | { 121 | method: 'send_invoice_reminder', 122 | name: 'Send Invoice Reminder', 123 | description: sendInvoiceReminderPrompt(context), 124 | parameters: sendInvoiceReminderParameters(context), 125 | actions: { 126 | invoices: { 127 | sendReminder: true, 128 | }, 129 | }, 130 | }, 131 | { 132 | method: 'cancel_sent_invoice', 133 | name: 'Cancel Sent Invoice', 134 | description: cancelSentInvoicePrompt(context), 135 | parameters: cancelSentInvoiceParameters(context), 136 | actions: { 137 | invoices: { 138 | cancel: true, 139 | }, 140 | }, 141 | }, 142 | { 143 | method: 'generate_invoice_qr_code', 144 | name: 'Generate Invoice QR Code', 145 | description: generateInvoiceQrCodePrompt(context), 146 | parameters: generateInvoiceQrCodeParameters(context), 147 | actions: { 148 | invoices: { 149 | generateQRC: true, 150 | }, 151 | }, 152 | }, 153 | { 154 | method: 'create_product', 155 | name: 'Create Product', 156 | description: createProductPrompt(context), 157 | parameters: createProductParameters(context), 158 | actions: { 159 | products: { 160 | create: true, 161 | }, 162 | }, 163 | }, 164 | { 165 | method: 'list_products', 166 | name: 'List Products', 167 | description: listProductsPrompt(context), 168 | parameters: listProductsParameters(context), 169 | actions: { 170 | products: { 171 | list: true, 172 | }, 173 | }, 174 | }, 175 | { 176 | method: 'update_product', 177 | name: 'Update Product', 178 | description: updateProductPrompt(context), 179 | parameters: updateProductParameters(context), 180 | actions: { 181 | products: { 182 | update: true, 183 | }, 184 | }, 185 | }, 186 | { 187 | method: 'show_product_details', 188 | name: 'Show Products Details', 189 | description: showProductDetailsPrompt(context), 190 | parameters: showProductDetailsParameters(context), 191 | actions: { 192 | products: { 193 | show: true, 194 | }, 195 | }, 196 | }, 197 | { 198 | method: 'create_subscription_plan', 199 | name: 'Create Subscription Plan', 200 | description: createSubscriptionPlanPrompt(context), 201 | parameters: createSubscriptionPlanParameters(context), 202 | actions: { 203 | subscriptionPlans: { 204 | create: true, 205 | }, 206 | }, 207 | }, 208 | { 209 | method: 'list_subscription_plans', 210 | name: 'List Subscription Plans', 211 | description: listSubscriptionPlansPrompt(context), 212 | parameters: listSubscriptionPlansParameters(context), 213 | actions: { 214 | subscriptionPlans: { 215 | list: true, 216 | }, 217 | }, 218 | }, 219 | { 220 | method: 'show_subscription_plan_details', 221 | name: 'Show Subscription Plan Details', 222 | description: showSubscriptionPlanDetailsPrompt(context), 223 | parameters: showSubscriptionPlanDetailsParameters(context), 224 | actions: { 225 | subscriptionPlans: { 226 | show: true, 227 | }, 228 | }, 229 | }, 230 | { 231 | method: 'create_subscription', 232 | name: 'Create Subscription', 233 | description: createSubscriptionPrompt(context), 234 | parameters: createSubscriptionParameters(context), 235 | actions: { 236 | subscriptions: { 237 | create: true, 238 | }, 239 | }, 240 | }, 241 | { 242 | method: 'show_subscription_details', 243 | name: 'Show Subscription Details', 244 | description: showSubscriptionDetailsPrompt(context), 245 | parameters: showSubscriptionDetailsParameters(context), 246 | actions: { 247 | subscriptions: { 248 | show: true, 249 | }, 250 | }, 251 | }, 252 | { 253 | method: 'cancel_subscription', 254 | name: 'Cancel Subscription', 255 | description: cancelSubscriptionPrompt(context), 256 | parameters: cancelSubscriptionParameters(context), 257 | actions: { 258 | subscriptions: { 259 | cancel: true, 260 | }, 261 | }, 262 | }, 263 | { 264 | method: 'create_shipment_tracking', 265 | name: 'Create shipment', 266 | description: createShipmentPrompt(context), 267 | parameters: createShipmentParameters(context), 268 | actions: { 269 | shipment: { 270 | create: true, 271 | }, 272 | }, 273 | }, 274 | { 275 | method: 'get_shipment_tracking', 276 | name: 'Get Shipment Tracking', 277 | description: getShipmentTrackingPrompt(context), 278 | parameters: getShipmentTrackingParameters(context), 279 | actions: { 280 | shipment: { 281 | get: true, 282 | }, 283 | }, 284 | }, 285 | { 286 | method: 'create_order', 287 | name: 'Create Order', 288 | description: createOrderPrompt(context), 289 | parameters: createOrderParameters(context), 290 | actions: { 291 | orders: { 292 | create: true, 293 | }, 294 | }, 295 | }, 296 | { 297 | method: 'get_order', 298 | name: 'Get Order', 299 | description: getOrderPrompt(context), 300 | parameters: getOrderParameters(context), 301 | actions: { 302 | orders: { 303 | get: true, 304 | }, 305 | }, 306 | }, 307 | { 308 | method: 'pay_order', 309 | name: 'Process payment for an authorized order', 310 | description: captureOrderPrompt(context), 311 | parameters: captureOrderParameters(context), 312 | actions: { 313 | orders: { 314 | capture: true, 315 | }, 316 | }, 317 | }, 318 | { 319 | method: 'list_disputes', 320 | name: 'List Disputes', 321 | description: listDisputesPrompt(context), 322 | parameters: listDisputesParameters(context), 323 | actions: { 324 | disputes: { 325 | list: true, 326 | }, 327 | }, 328 | }, 329 | { 330 | method: 'get_dispute', 331 | name: 'Get Dispute', 332 | description: getDisputePrompt(context), 333 | parameters: getDisputeParameters(context), 334 | actions: { 335 | disputes: { 336 | get: true, 337 | }, 338 | }, 339 | }, 340 | { 341 | method: 'accept_dispute_claim', 342 | name: 'Accept dispute claim', 343 | description: acceptDisputeClaimPrompt(context), 344 | parameters: acceptDisputeClaimParameters(context), 345 | actions: { 346 | disputes: { 347 | create: true, 348 | }, 349 | }, 350 | }, 351 | { 352 | method: 'list_transactions', 353 | name: 'List Transactions', 354 | description: listTransactionsPrompt(context), 355 | parameters: listTransactionsParameters(context), 356 | actions: { 357 | transactions: { 358 | list: true, 359 | }, 360 | }, 361 | }, 362 | ]; 363 | const allActions = tools({}).reduce((acc, tool) => { 364 | Object.keys(tool.actions).forEach(product => { 365 | acc[product] = { ...acc[product], ...tool.actions[product] }; 366 | }); 367 | return acc; 368 | }, {} as { [key: string]: { [key: string]: boolean } }); 369 | 370 | export const ALL_TOOLS_ENABLED = allActions; 371 | 372 | export default tools; 373 | -------------------------------------------------------------------------------- /typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "moduleDetection": "force", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "declarationDir": "./dist/types", 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "skipLibCheck": true 15 | }, 16 | "include": [ 17 | "**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | "dist" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /typescript/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup'; 2 | 3 | export default defineConfig([{ // Define multiple entry points 4 | entry: ["src/ai-sdk/index.ts"], 5 | outDir: "ai-sdk", 6 | format: ["cjs", "esm"], // Output both CommonJS and ESM 7 | dts: true, 8 | clean: true, // Clean dist before building 9 | bundle: true, // Bundle dependencies; 10 | splitting: false, // Prevent code splitting for simplicity 11 | minify: true, // Minify output files 12 | }, 13 | { 14 | entry: ['src/modelcontextprotocol/index.ts'], 15 | outDir: "mcp", 16 | clean: true, 17 | dts: true, 18 | format: ['cjs', 'esm'], 19 | sourcemap: true, 20 | target: 'node18', 21 | } 22 | ]); --------------------------------------------------------------------------------