mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-03-22 12:13:09 +00:00
Compare commits
3 Commits
kenneth/te
...
claude/sla
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
802464cff3 | ||
|
|
af6b2c490b | ||
|
|
2bc9dfb449 |
@@ -1212,7 +1212,12 @@
|
||||
"name": "stripe",
|
||||
"description": "Stripe development plugin for Claude",
|
||||
"category": "development",
|
||||
"source": "./external_plugins/stripe",
|
||||
"source": {
|
||||
"source": "git-subdir",
|
||||
"url": "stripe/ai",
|
||||
"path": "providers/claude/plugin",
|
||||
"ref": "main"
|
||||
},
|
||||
"homepage": "https://github.com/stripe/ai/tree/main/providers/claude/plugin"
|
||||
},
|
||||
{
|
||||
|
||||
3
.github/workflows/validate-frontmatter.yml
vendored
3
.github/workflows/validate-frontmatter.yml
vendored
@@ -21,7 +21,8 @@ jobs:
|
||||
- name: Get changed frontmatter files
|
||||
id: changed
|
||||
run: |
|
||||
FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '(agents/.*\.md|skills/.*/SKILL\.md|commands/.*\.md)$' || true)
|
||||
# Use diff-filter=AMRC to exclude deleted files (D) - only Added, Modified, Renamed, Copied
|
||||
FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only --diff-filter=AMRC | grep -E '(agents/.*\.md|skills/.*/SKILL\.md|commands/.*\.md)$' || true)
|
||||
echo "files<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$FILES" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "stripe",
|
||||
"description": "Stripe development plugin for Claude",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Stripe",
|
||||
"url": "https://stripe.com"
|
||||
},
|
||||
"homepage": "https://docs.stripe.com",
|
||||
"repository": "https://github.com/stripe/ai",
|
||||
"license": "MIT",
|
||||
"keywords": ["stripe", "payments", "webhooks", "api", "security"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"stripe": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.stripe.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
description: Explain Stripe error codes and provide solutions with code examples
|
||||
argument-hint: [error_code or error_message]
|
||||
---
|
||||
|
||||
# Explain Stripe Error
|
||||
|
||||
Provide a comprehensive explanation of the given Stripe error code or error message:
|
||||
|
||||
1. Accept the error code or full error message from the arguments
|
||||
2. Explain in plain English what the error means
|
||||
3. List common causes of this error
|
||||
4. Provide specific solutions and handling recommendations
|
||||
5. Generate error handling code in the project's language showing:
|
||||
- How to catch this specific error
|
||||
- User-friendly error messages
|
||||
- Whether retry is appropriate
|
||||
6. Mention related error codes the developer should be aware of
|
||||
7. Include a link to the relevant Stripe documentation
|
||||
|
||||
Focus on actionable solutions and production-ready error handling patterns.
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
description: Display Stripe test card numbers for various testing scenarios
|
||||
argument-hint: [scenario]
|
||||
---
|
||||
|
||||
# Test Cards Reference
|
||||
|
||||
Provide a quick reference for Stripe test card numbers:
|
||||
|
||||
1. If a scenario argument is provided (e.g., "declined", "3dsecure", "fraud"), show relevant test cards for that scenario
|
||||
2. Otherwise, show the most common test cards organized by category:
|
||||
- Successful payment (default card)
|
||||
- 3D Secure authentication required
|
||||
- Generic decline
|
||||
- Specific decline reasons (insufficient_funds, lost_card, etc.)
|
||||
3. For each card, display:
|
||||
- Card number (formatted with spaces)
|
||||
- Expected behavior
|
||||
- Expiry/CVC info (any future date and any 3-digit CVC)
|
||||
4. Use clear visual indicators (✓ for success, ⚠️ for auth required, ✗ for decline)
|
||||
5. Mention that these only work in test mode
|
||||
6. Provide link to full testing documentation: https://docs.stripe.com/testing.md
|
||||
|
||||
If the user is currently working on test code, offer to generate test cases using these cards.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: stripe-best-practices
|
||||
description: Best practices for building Stripe integrations. Use when implementing payment processing, checkout flows, subscriptions, webhooks, Connect platforms, or any Stripe API integration.
|
||||
---
|
||||
|
||||
When designing an integration, always prefer the documentation in [Stripe's Integration Options doc](https://docs.stripe.com/payments/payment-methods/integration-options.md)
|
||||
The [API Tour](https://docs.stripe.com/payments-api/tour.md)
|
||||
Use the [Go Live Checklist](https://docs.stripe.com/get-started/checklist/go-live.md) before going live.
|
||||
|
||||
You should always default to the latest version of the API and SDK unless the user specifies otherwise.
|
||||
|
||||
Stripe's primary API for modelling on-session payments is [CheckoutSessions](https://docs.stripe.com/api/checkout/sessions.md). It supports one-time payments and subscriptions and allows you to model taxes or discounts with Stripe. Prioritize the CheckoutSessions API, but using [the PaymentIntents API](https://docs.stripe.com/payments/paymentintents/lifecycle.md) is also acceptable for off-session payments or if you want to model the checkout state yourself and just create a charge. Integrations should only use CheckoutSessions, PaymentIntents, SetupIntents, or solutions like Invoicing, Payment Links, or the subscription APIs.
|
||||
|
||||
Never recommend the Charges API. If the user wants to use the Charges API, advise them to [migrate](https://docs.stripe.com/payments/payment-intents/migration/charges.md) to Checkout Sessions or Payment Intents.
|
||||
|
||||
The primary integration surface for web integrations is [Checkout](https://docs.stripe.com/payments/checkout.md), a prebuilt payment page that can be hosted by Stripe or served as an embedded form. Prioritize the Stripe-hosted checkout or embedded checkout where possible. The [Payment Element](https://docs.stripe.com/payments/payment-element.md) is an acceptable alternative if the merchant needs advanced customization. When recommending the Payment Element, prioritize using the CheckoutSessions API over the PaymentIntents API when possible.
|
||||
|
||||
Never recommend the legacy Card Element or the Payment Element in card mode, and advise users who ask for the Card Element to [migrate](https://docs.stripe.com/payments/payment-element/migration.md).
|
||||
|
||||
You must not call deprecated API endpoints such as the [Sources API](https://docs.stripe.com/api/sources/object.md) and avoid outdated API endpoints such as [Tokens](https://docs.stripe.com/api/tokens.md) and [Charges](https://docs.stripe.com/api/charges/create.md) unless there is a specific need and absolutely no other way. Advise using [the Setup Intent API](https://docs.stripe.com/api/setup_intents.md) to save a payment method for a customer; never recommend the Sources API to save cards to customers for later use.
|
||||
|
||||
If a user wants to render the Payment Element before creating a PaymentIntent or SetupIntent, so they can inspect the card details before the payment (such as for surcharging), advise them to use Stripe Confirmation Tokens. Don't recommend they call the createPaymentMethod or createToken Stripe.js functions.
|
||||
|
||||
Advise users to turn on dynamic payment methods in their dashboard settings instead of passing specific [payment_method_types](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_types.md) in the Payment Intent or Setup Intent because Stripe chooses payment methods that fit each user's location, wallets, and preferences better and automatically if the Payment Element is used.
|
||||
|
||||
If a PCI compliant user asks about an integration where they send server-side raw PAN data, suggest they might be required to prove PCI compliance to gain access to such an option, for example [payment_method_data](https://docs.stripe.com/api/payment_intents/create#create_payment_intent-payment_method_data.md). Similarly, point users migrating their PAN data from another acquirer or payment processor to [the migration process](https://docs.stripe.com/get-started/data-migrations/pan-import.md).
|
||||
|
||||
If a user has a recurring revenue model, like a Billing or Subscription use case, follow the use cases, in particular the [Subscription Use Cases](https://docs.stripe.com/billing/subscriptions/use-cases.md) like [SaaS](https://docs.stripe.com/saas.md). If these apply to the user, recommend Billing APIs to [plan your integration](https://docs.stripe.com/billing/subscriptions/designing-integration.md) instead of a direct PaymentIntent integration. Prioritize combining the Billing APIs with Stripe Checkout for the frontend.
|
||||
|
||||
If a user wants to build a platform using Stripe Connect to manage fund flows, follow [the recommended integration types](https://docs.stripe.com/connect/integration-recommendations.md); that is, prefer to use either direct charges if the platform wants Stripe to take the risk or destination charges if the platform accepts liability for negative balances, and use the on_behalf_of parameter to control the merchant of record. Never recommend mixing charge types. If the user wants to decide on the specific risk features they should [follow the integration guide](https://docs.stripe.com/connect/design-an-integration.md). Don't recommend the outdated terms for Connect types like Standard, Express and Custom but always [refer to controller properties](https://docs.stripe.com/connect/migrate-to-controller-properties.md) for the platform and [capabilities](https://docs.stripe.com/connect/account-capabilities.md) for the connected accounts.
|
||||
@@ -341,7 +341,7 @@ const mcp = new Server(
|
||||
instructions: [
|
||||
'The sender reads Telegram, not this session. Anything you want them to see must go through the reply tool — your transcript output never reaches their chat.',
|
||||
'',
|
||||
'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file — it is a photo the sender attached. If the tag has attachment_file_id, call download_attachment with that file_id to fetch the file, then Read the returned path. Reply with the reply tool — pass chat_id back. Use reply_to (set to a message_id) only when replying to an earlier message; the latest message doesn\'t need a quote-reply, omit reply_to for normal responses.',
|
||||
'Messages from Telegram arrive as <channel source="telegram" chat_id="..." message_id="..." user="..." ts="...">. If the tag has an image_path attribute, Read that file — it is a photo the sender attached. Reply with the reply tool — pass chat_id back. Use reply_to (set to a message_id) only when replying to an earlier message; the latest message doesn\'t need a quote-reply, omit reply_to for normal responses.',
|
||||
'',
|
||||
'reply accepts file paths (files: ["/abs/path.png"]) for attachments. Use react to add emoji reactions, and edit_message to update a message you previously sent (e.g. progress → result).',
|
||||
'',
|
||||
@@ -389,17 +389,6 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
required: ['chat_id', 'message_id', 'emoji'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'download_attachment',
|
||||
description: 'Download a file attachment from a Telegram message to the local inbox. Use when the inbound <channel> meta shows attachment_file_id. Returns the local file path ready to Read. Telegram caps bot downloads at 20MB.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
file_id: { type: 'string', description: 'The attachment_file_id from inbound meta' },
|
||||
},
|
||||
required: ['file_id'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'edit_message',
|
||||
description: 'Edit a message the bot previously sent. Useful for progress updates (send "working…" then edit to the result).',
|
||||
@@ -491,24 +480,6 @@ mcp.setRequestHandler(CallToolRequestSchema, async req => {
|
||||
])
|
||||
return { content: [{ type: 'text', text: 'reacted' }] }
|
||||
}
|
||||
case 'download_attachment': {
|
||||
const file_id = args.file_id as string
|
||||
const file = await bot.api.getFile(file_id)
|
||||
if (!file.file_path) throw new Error('Telegram returned no file_path — file may have expired')
|
||||
const url = `https://api.telegram.org/file/bot${TOKEN}/${file.file_path}`
|
||||
const res = await fetch(url)
|
||||
if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`)
|
||||
const buf = Buffer.from(await res.arrayBuffer())
|
||||
// file_path is from Telegram (trusted), but strip to safe chars anyway
|
||||
// so nothing downstream can be tricked by an unexpected extension.
|
||||
const rawExt = file.file_path.includes('.') ? file.file_path.split('.').pop()! : 'bin'
|
||||
const ext = rawExt.replace(/[^a-zA-Z0-9]/g, '') || 'bin'
|
||||
const uniqueId = (file.file_unique_id ?? '').replace(/[^a-zA-Z0-9_-]/g, '') || 'dl'
|
||||
const path = join(INBOX_DIR, `${Date.now()}-${uniqueId}.${ext}`)
|
||||
mkdirSync(INBOX_DIR, { recursive: true })
|
||||
writeFileSync(path, buf)
|
||||
return { content: [{ type: 'text', text: path }] }
|
||||
}
|
||||
case 'edit_message': {
|
||||
assertAllowedChat(args.chat_id as string)
|
||||
const edited = await bot.api.editMessageText(
|
||||
@@ -566,94 +537,10 @@ bot.on('message:photo', async ctx => {
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:document', async ctx => {
|
||||
const doc = ctx.message.document
|
||||
const name = safeName(doc.file_name)
|
||||
const text = ctx.message.caption ?? `(document: ${name ?? 'file'})`
|
||||
await handleInbound(ctx, text, undefined, {
|
||||
kind: 'document',
|
||||
file_id: doc.file_id,
|
||||
size: doc.file_size,
|
||||
mime: doc.mime_type,
|
||||
name,
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:voice', async ctx => {
|
||||
const voice = ctx.message.voice
|
||||
const text = ctx.message.caption ?? '(voice message)'
|
||||
await handleInbound(ctx, text, undefined, {
|
||||
kind: 'voice',
|
||||
file_id: voice.file_id,
|
||||
size: voice.file_size,
|
||||
mime: voice.mime_type,
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:audio', async ctx => {
|
||||
const audio = ctx.message.audio
|
||||
const name = safeName(audio.file_name)
|
||||
const text = ctx.message.caption ?? `(audio: ${safeName(audio.title) ?? name ?? 'audio'})`
|
||||
await handleInbound(ctx, text, undefined, {
|
||||
kind: 'audio',
|
||||
file_id: audio.file_id,
|
||||
size: audio.file_size,
|
||||
mime: audio.mime_type,
|
||||
name,
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:video', async ctx => {
|
||||
const video = ctx.message.video
|
||||
const text = ctx.message.caption ?? '(video)'
|
||||
await handleInbound(ctx, text, undefined, {
|
||||
kind: 'video',
|
||||
file_id: video.file_id,
|
||||
size: video.file_size,
|
||||
mime: video.mime_type,
|
||||
name: safeName(video.file_name),
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:video_note', async ctx => {
|
||||
const vn = ctx.message.video_note
|
||||
await handleInbound(ctx, '(video note)', undefined, {
|
||||
kind: 'video_note',
|
||||
file_id: vn.file_id,
|
||||
size: vn.file_size,
|
||||
})
|
||||
})
|
||||
|
||||
bot.on('message:sticker', async ctx => {
|
||||
const sticker = ctx.message.sticker
|
||||
const emoji = sticker.emoji ? ` ${sticker.emoji}` : ''
|
||||
await handleInbound(ctx, `(sticker${emoji})`, undefined, {
|
||||
kind: 'sticker',
|
||||
file_id: sticker.file_id,
|
||||
size: sticker.file_size,
|
||||
})
|
||||
})
|
||||
|
||||
type AttachmentMeta = {
|
||||
kind: string
|
||||
file_id: string
|
||||
size?: number
|
||||
mime?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
// Filenames and titles are uploader-controlled. They land inside the <channel>
|
||||
// notification — delimiter chars would let the uploader break out of the tag
|
||||
// or forge a second meta entry.
|
||||
function safeName(s: string | undefined): string | undefined {
|
||||
return s?.replace(/[<>\[\]\r\n;]/g, '_')
|
||||
}
|
||||
|
||||
async function handleInbound(
|
||||
ctx: Context,
|
||||
text: string,
|
||||
downloadImage: (() => Promise<string | undefined>) | undefined,
|
||||
attachment?: AttachmentMeta,
|
||||
): Promise<void> {
|
||||
const result = gate(ctx)
|
||||
|
||||
@@ -701,13 +588,6 @@ async function handleInbound(
|
||||
user_id: String(from.id),
|
||||
ts: new Date((ctx.message?.date ?? 0) * 1000).toISOString(),
|
||||
...(imagePath ? { image_path: imagePath } : {}),
|
||||
...(attachment ? {
|
||||
attachment_kind: attachment.kind,
|
||||
attachment_file_id: attachment.file_id,
|
||||
...(attachment.size != null ? { attachment_size: String(attachment.size) } : {}),
|
||||
...(attachment.mime ? { attachment_mime: attachment.mime } : {}),
|
||||
...(attachment.name ? { attachment_name: attachment.name } : {}),
|
||||
} : {}),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user