Skip to content

feat: add additional MCP server support with x402 batch payment example#434

Open
plagtech wants to merge 5 commits into
stripe:mainfrom
plagtech:feat/x402-batch-payments
Open

feat: add additional MCP server support with x402 batch payment example#434
plagtech wants to merge 5 commits into
stripe:mainfrom
plagtech:feat/x402-batch-payments

Conversation

@plagtech
Copy link
Copy Markdown

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

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

cd tools/typescript
pnpm test

@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented May 24, 2026

CLA assistant check
All committers have signed the CLA.

@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented May 24, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@TateLyman
Copy link
Copy Markdown

Reviewed the multi-MCP routing path from an agent-payment safety angle. The extension point is useful, but I would add a collision guard before this is exposed as a payment-tool pattern.

Right now additional MCP servers are merged into allTools, and routeToolCall() checks additionalClient.hasTool(name) before falling back to Stripe:

if (this.additionalClient && this.additionalClient.hasTool(name)) {
  return this.additionalClient.callTool(name, args);
}
return this.mcpClient.callTool(name, args, options);

If an additional server exposes a tool with the same name as a Stripe core tool, it can shadow the Stripe tool at execution time. Depending on framework behavior, duplicate function/tool names may also produce invalid or ambiguous tool schemas for the model. For a payment toolkit, that is a risky default because an untrusted or misconfigured external MCP server could receive calls the developer expected to go to Stripe.

Suggested fix before merge:

  • reject duplicate tool names during connectServer() / merge, or namespace external tools as <server>__<tool>;
  • make the routing order match the documented trust boundary, preferably core Stripe first unless explicitly overridden;
  • add a unit test where an additional server returns create_payment_link or another Stripe tool name and assert deterministic failure/namespace behavior.

This is docs/code review only; I did not call external MCP servers or payment endpoints.

@plagtech
Copy link
Copy Markdown
Author

Good catch — you're right that additional tools shouldn't be able to shadow core Stripe operations. Pushed a fix:

Collision guard: MultiMcpClient.setReservedNames() receives all Stripe tool names before connecting additional servers. Any tool that shares a name with a Stripe tool is silently skipped with a console warning.
Routing: Updated comments — additional servers only serve tools Stripe doesn't own. The collision guard prevents duplicates from registering, and the routing provides defense-in-depth.
Test: Added a case where an additional server returns create_payment_link alongside batch tools. Asserts the colliding tool is rejected and only non-colliding tools are accessible.

@TateLyman
Copy link
Copy Markdown

Thanks for patching the collision case. I checked the new head and the guard looks directionally right: core Stripe names are reserved before additional MCP servers connect, and colliding external tools are skipped before routing can reach them.

The current blocker appears to be CI/lint rather than the collision design itself. The failing TypeScript job is pnpm run lint; the actionable failures are mostly formatter/style plus two example-loop rules:

  • examples/batch-payments/index.ts: no-process-exit at line 32, no-await-in-loop at lines 74/84, plus Prettier formatting at lines 57/97.
  • src/openai/toolkit.ts, src/shared/multi-mcp-client.ts, src/shared/toolkit-core.ts, and src/test/shared/multi-mcp-client.test.ts: Prettier formatting.
  • src/shared/toolkit-core.ts: routeToolCall is marked async without an await; either return the promise without async, or await the selected client call.
  • src/test/shared/multi-mcp-client.test.ts: no-plusplus around line 183.

I did not call any external MCP servers or payment endpoints. This is based on the GitHub Actions log for the failing TypeScript job on 0bb3da2.

@plagtech
Copy link
Copy Markdown
Author

Thanks for the detailed breakdown — really appreciate you pulling the CI log. Pushing fixes now:

  1. Prettier formatting across all touched files
  2. no-process-exit → replacing process.exit(1) with a thrown error in the example
  3. no-await-in-loop → refactoring the sequential tool call loop in the example
  4. routeToolCall → adding await on the client calls
  5. no-plusplus → switching to += 1 in the test

Will update shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants