Skip to content

Conversation

@sgr-stripe
Copy link
Contributor

What Changed

Before: The library executed Stripe API operations locally. When you called a tool like create_customer, the library directly used the Stripe SDK (stripe.customers.create()) to make the API call. All tool definitions and their execution logic were bundled in the library itself.

After: The library is now a thin client that connects to Stripe's MCP server (mcp.stripe.com). When you call a tool, it:

  1. Fetches available tools from the remote server at initialization
  2. Sends tool execution requests to the remote server
  3. Returns the response

Key architectural changes:

  • New src/shared/mcp-client.ts: Handles WebSocket-like connection to mcp.stripe.com
  • src/shared/api.ts: Changed from "local executor" to "remote proxy"
  • All framework integrations (ai-sdk, langchain, openai, modelcontextprotocol, cloudflare): Added async initialization

What Changed for Users

Breaking change - Async initialization is now required:

  // Before (v0.8.x) - Synchronous
  const toolkit = new StripeAgentToolkit({ secretKey, configuration });
  const tools = toolkit.getTools();
  // After (v0.9.0) - Must await initialization
  const toolkit = new StripeAgentToolkit({ secretKey, configuration });
  await toolkit.initialize();  // NEW - Required
  const tools = toolkit.getTools();
  // Or use the new factory function
  const toolkit = await createStripeAgentToolkit({ secretKey, configuration });

  New behaviors:
  - Deprecation warning if using sk_* keys (should use restricted keys rk_*)
  - Hard failure if MCP server is unreachable (no silent fallback)
  - Must call toolkit.close() to clean up connections

Why These Changes Were Necessary

  1. Single source of truth: Tool definitions and behaviors now live on Stripe's server. When Stripe adds new tools or updates existing ones, users get them automatically without updating the library.
  2. Security: Using mcp.stripe.com enables OAuth authentication flows and proper permission scoping that weren't possible with direct SDK calls.
  3. Consistency: The library now uses the same MCP protocol as other Stripe integrations (like Claude Desktop via the official Stripe MCP server), ensuring consistent behavior.
  4. Reduced maintenance: No need to maintain local copies of tool definitions, parameter schemas, and execution logic.

Why the Breaking Change Was Required

The async initialization is unavoidable because:

  1. Network I/O: Fetching tool definitions from mcp.stripe.com requires a network call. JavaScript has no synchronous network operations (and shouldn't - it would block the event loop).
  2. Connection setup: The MCP protocol uses a persistent connection that must be established before tools can be used.
  3. No workaround: We can't lazily initialize on first tool access because by that point the user already needs the tool definitions to configure their AI framework.

The alternative would have been to bundle a static snapshot of tool definitions and only use MCP for execution - but this defeats the purpose of having tools update dynamically.

secretKey,
context: {
account: configuration.context?.account,
customer: configuration.context?.customer,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, this is kind of a monkey wrench, this will not work as well

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/stripe/ai/blob/main/tools/typescript/src/shared/invoices/listInvoices.ts#L12-L20 , some of these use the customer parameter to filter out results per a specific customer if you pass that in

// Keep Stripe SDK for registerPaidTool billing operations
this._stripe = new Stripe(secretKey, {
appInfo: {
name: 'stripe-mcp',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name is important for metrics

}

if (config.context?.customer) {
headers['X-Stripe-Customer'] = config.context.customer;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't do anything

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.

3 participants