Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# feat: Add support for additional MCP servers with x402 batch payment example

## Problem

The Stripe Agent Toolkit currently connects exclusively to `mcp.stripe.com` for its tools. While the core Stripe tools handle individual operations well (create_payment_link, create_invoice, etc.), agents frequently need to perform **batch operations** — paying multiple contractors, splitting revenue across recipients, or processing bulk refunds.

Today, an agent handling "pay these 5 contractors" must loop through individual `create_transfer` calls. There's no batch primitive, and no way to extend the toolkit with additional payment capabilities from external providers.

## Solution

This PR adds support for **additional MCP servers** whose tools are merged with the core Stripe tools. This enables:

1. **Extensibility**: The toolkit can now surface tools from any MCP-compatible server alongside Stripe's core tools
2. **Automatic routing**: Tool calls are transparently routed to the correct server — agents don't need to know which server provides which tool
3. **Batch payments**: The included example demonstrates x402 batch payment tools via [Spraay Protocol](https://spraay.app), enabling multi-recipient on-chain transfers in a single transaction

### How it works

```typescript
const toolkit = await createStripeAgentToolkit({
secretKey: process.env.STRIPE_SECRET_KEY!,
configuration: {
additionalMcpServers: [
{
name: 'Spraay Protocol',
url: 'https://mcp.spraay.app',
},
],
},
});

// Tools from both Stripe and additional servers are available
const tools = toolkit.getTools();
```

When the agent calls a tool:
- If the tool came from an additional server → routed to that server
- Otherwise → routed to `mcp.stripe.com` as before

### Why x402

Stripe already supports the x402 payment protocol on Base. This PR extends that support into the agent toolkit by enabling x402-based batch payments — settling multiple transfers in a single on-chain transaction via USDC.

## Changes

### Core changes (backward compatible)

- **`configuration.ts`**: Added `AdditionalMcpServer` type and `additionalMcpServers` config option
- **`multi-mcp-client.ts`**: New client that manages connections to additional MCP servers with tool routing
- **`toolkit-core.ts`**: Extended `initialize()` to connect additional servers; added `routeToolCall()` for transparent routing

### Framework updates

- **`openai/toolkit.ts`**: Updated `handleToolCall()` to use `routeToolCall()` for correct routing
- **`langchain/toolkit.ts`**: Updated `StripeTool` to route through `ToolkitCore` instead of direct `mcpClient` access
- **`ai-sdk/toolkit.ts`**: Updated `execute` callbacks to use `routeToolCall()`

### Example & tests

- **`examples/batch-payments/`**: Complete example showing x402 batch payments with Spraay Protocol
- **`test/shared/multi-mcp-client.test.ts`**: Unit tests for the multi-MCP client

## Backward compatibility

- **Zero breaking changes**: The `additionalMcpServers` config is optional and defaults to `undefined`
- **Existing behavior preserved**: Without additional servers, the toolkit behaves identically to before
- **All existing tests pass**: Core MCP client behavior is unchanged

## Testing

```bash
cd tools/typescript
pnpm test
```
4 changes: 4 additions & 0 deletions tools/typescript/examples/batch-payments/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
STRIPE_SECRET_KEY=rk_test_...
OPENAI_API_KEY=sk-...
SPRAAY_MCP_URL=
SPRAAY_API_KEY=
62 changes: 62 additions & 0 deletions tools/typescript/examples/batch-payments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Batch Payments Example

Extends the Stripe Agent Toolkit with batch payment tools from [Spraay Protocol](https://spraay.app) — an x402 batch payment gateway supporting 13 chains.

## What this enables

- **Payroll**: Pay multiple contractors in one transaction
- **Revenue sharing**: Split payments across multiple recipients
- **Multi-vendor payments**: Transfer funds to multiple suppliers
- **Batch invoicing**: Generate invoices for multiple recipients

## Spraay Protocol

| Resource | URL |
| ---------------------- | ------------------------------------------------------------------------------------ |
| x402 Gateway (live) | https://gateway.spraay.app |
| MCP Server (120 tools) | https://smithery.ai/server/@plagtech/spraay-x402-mcp |
| npm | [@plagtech/spraay-x402-mcp](https://www.npmjs.com/package/@plagtech/spraay-x402-mcp) |
| Documentation | https://docs.spraay.app |

## Setup

```bash
npm install
```

Copy `.env.template` to `.env` and fill in:

```
STRIPE_SECRET_KEY=rk_test_...
OPENAI_API_KEY=sk-...
SPRAAY_MCP_URL= # Your Spraay MCP server endpoint
SPRAAY_API_KEY= # Optional API key
```

## Run

```bash
npx ts-node index.ts
```

## How it works

Additional MCP servers are configured via the `additionalMcpServers` option:

```typescript
const toolkit = await createStripeAgentToolkit({
secretKey: process.env.STRIPE_SECRET_KEY!,
configuration: {
additionalMcpServers: [
{
name: 'Spraay Protocol',
url: process.env.SPRAAY_MCP_URL,
},
],
},
});
```

Tools from both Stripe and additional servers are merged into a single list. When the agent calls a tool, the toolkit automatically routes to the correct server.

This pattern works with any MCP-compatible server — Spraay is one example providing batch payment infrastructure that complements Stripe's core tools.
96 changes: 96 additions & 0 deletions tools/typescript/examples/batch-payments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {createStripeAgentToolkit} from '@stripe/agent-toolkit/openai';
import OpenAI from 'openai';
import type {ChatCompletionMessageParam} from 'openai/resources';

require('dotenv').config();

const openai = new OpenAI();

/**
* Example: Extending the Stripe Agent Toolkit with batch payment tools
* via an additional MCP server.
*
* This example uses Spraay Protocol (https://spraay.app) — an x402 batch
* payment gateway supporting 13 chains, payroll, invoicing, and multi-
* recipient transfers in a single on-chain transaction.
*
* Spraay's x402 gateway: https://gateway.spraay.app
* MCP server (Smithery): https://smithery.ai/server/@plagtech/spraay-x402-mcp
* npm: @plagtech/spraay-x402-mcp
*
* Use cases:
* - Payroll: "Pay all 5 contractors from this invoice batch"
* - Multi-vendor: "Transfer USDC to these 3 suppliers"
* - Revenue sharing: "Split this payment across 4 recipients"
*/
async function main(): Promise<void> {
if (!process.env.SPRAAY_MCP_URL) {
throw new Error(
'Set SPRAAY_MCP_URL to your Spraay MCP server endpoint. ' +
'See: https://smithery.ai/server/@plagtech/spraay-x402-mcp'
);
}

const toolkit = await createStripeAgentToolkit({
secretKey: process.env.STRIPE_SECRET_KEY!,
configuration: {
additionalMcpServers: [
{
name: 'Spraay Protocol',
url: process.env.SPRAAY_MCP_URL,
headers: {
...(process.env.SPRAAY_API_KEY && {
'X-API-KEY': process.env.SPRAAY_API_KEY,
}),
},
},
],
},
});

const tools = toolkit.getTools();
console.log(`Loaded ${tools.length} tools (Stripe core + batch payments)\n`);

const messages: ChatCompletionMessageParam[] = [
{
role: 'user',
content: [
"I need to pay three contractors for this month's work:",
'- Alice (0x1234...abcd): $500 USDC',
'- Bob (0x5678...efgh): $750 USDC',
'- Carol (0x9abc...ijkl): $300 USDC',
'',
'Use a batch transfer on Base to pay them all in one transaction.',
].join('\n'),
},
];

async function step(
msgs: ChatCompletionMessageParam[]
): Promise<ChatCompletionMessageParam[]> {
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: msgs,
tools,
});

const message = completion.choices[0].message;
const updated = [...msgs, message];

if (!message.tool_calls) {
console.log('Agent response:', message.content);
return updated;
}

const toolMessages = await Promise.all(
message.tool_calls.map((tc) => toolkit.handleToolCall(tc))
);

return step([...updated, ...toolMessages]);
}

await step(messages);
await toolkit.close();
}

main().catch(console.error);
19 changes: 19 additions & 0 deletions tools/typescript/examples/batch-payments/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "stripe-agent-toolkit-examples-batch-payments",
"version": "0.1.0",
"description": "Example: extending the Stripe Agent Toolkit with x402 batch payment tools",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"dotenv": "^16.4.5",
"openai": "^4.86.1",
"@stripe/agent-toolkit": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.7.4"
}
}
8 changes: 8 additions & 0 deletions tools/typescript/examples/batch-payments/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["index.ts"],
"exclude": ["node_modules", "dist"]
}
16 changes: 16 additions & 0 deletions tools/typescript/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tools/typescript/src/ai-sdk/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class StripeAgentToolkit extends ToolkitCore<Record<string, ProviderTool>> {
description: remoteTool.description || remoteTool.name,
inputSchema: zodSchema,
execute: (args: z.infer<typeof zodSchema>) => {
return this.mcpClient.callTool(remoteTool.name, args);
return this.routeToolCall(remoteTool.name, args);
},
});
}
Expand Down
25 changes: 19 additions & 6 deletions tools/typescript/src/langchain/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@ import {CallbackManagerForToolRun} from '@langchain/core/callbacks/manager';
import {RunnableConfig} from '@langchain/core/runnables';
import {jsonSchemaToZod} from '../shared/schema-utils';
import {ToolkitCore, ToolkitConfig, McpTool} from '../shared/toolkit-core';
import type {StripeMcpClient} from '../shared/mcp-client';

/**
* A function that executes a tool call and returns the result.
*/
type ToolCallFn = (
name: string,
args: Record<string, unknown>
) => Promise<string>;

/**
* A LangChain StructuredTool that executes Stripe operations via MCP.
* Routes tool calls through ToolkitCore to reach the correct MCP server.
*/
class StripeTool extends StructuredTool {
private mcpClient: StripeMcpClient;
private callToolFn: ToolCallFn;
method: string;
name: string;
description: string;
schema: z.ZodObject<any, any, any, any>;

constructor(
mcpClient: StripeMcpClient,
callToolFn: ToolCallFn,
method: string,
description: string,
schema: z.ZodObject<any, any, any, any>
) {
super();
this.mcpClient = mcpClient;
this.callToolFn = callToolFn;
this.method = method;
this.name = method;
this.description = description;
Expand All @@ -35,7 +43,7 @@ class StripeTool extends StructuredTool {
_runManager?: CallbackManagerForToolRun,
_parentConfig?: RunnableConfig
): Promise<any> {
return this.mcpClient.callTool(this.method, arg);
return this.callToolFn(this.method, arg);
}
}

Expand All @@ -58,10 +66,15 @@ class StripeAgentToolkit
}

protected convertTools(mcpTools: McpTool[]): StripeTool[] {
// Bind the routing function so tools from additional servers
// are routed correctly
const callToolFn: ToolCallFn = (name, args) =>
this.routeToolCall(name, args);

return mcpTools.map((tool) => {
const zodSchema = jsonSchemaToZod(tool.inputSchema);
return new StripeTool(
this.mcpClient,
callToolFn,
tool.name,
tool.description || tool.name,
zodSchema
Expand Down
6 changes: 2 additions & 4 deletions tools/typescript/src/openai/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,15 @@ class StripeAgentToolkit extends ToolkitCore<ChatCompletionTool[]> {

/**
* Processes a single OpenAI tool call by executing the requested function.
* Automatically routes to the correct MCP server (Stripe or additional).
*/
async handleToolCall(
toolCall: ChatCompletionMessageToolCall
): Promise<ChatCompletionToolMessageParam> {
this.ensureInitialized();

const args = JSON.parse(toolCall.function.arguments);
const response = await this.mcpClient.callTool(
toolCall.function.name,
args
);
const response = await this.routeToolCall(toolCall.function.name, args);
return {
role: 'tool',
tool_call_id: toolCall.id,
Expand Down
Loading
Loading