From e0fa0c121c1777d6ed309ed3e5c35b38bf0705f0 Mon Sep 17 00:00:00 2001 From: Aaron Sachs <898627+asachs01@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:50:40 +0000 Subject: [PATCH] =?UTF-8?q?feat(stripe,slack):=20flesh=20out=20plugin=20co?= =?UTF-8?q?ntent=20=E2=80=94=20skills,=20agents,=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #120 scaffolded Stripe + Slack (plugin.json + .mcp.json + README, empty skills/agents/commands dirs). This fills them with domain-appropriate, strong-first-pass content (mirrors the huntress structure/depth bar, NOT its security domain). Stripe (payments): skills payments / subscriptions / disputes; agent stripe-billing-support (confirm-before-write money discipline); commands /dispute-triage + /subscription-audit. Slack (messaging): skills messaging / channels-users / threads-reactions; agent slack-workspace-assistant (resolve-then-act, posting restraint); commands /channel-digest + /find-discussions. Bumped plugin.json 0.1.0->0.2.0 (both) + marketplace.json 1.10.0->1.11.0; regenerated docs/src/data/plugins.ts (data only; 66 insertions, stripe/slack only, no drift). Tool names are the gateway-prefixed *__ form; exact names to confirm vs a live tools/list (authored without live creds per overnight scope). Deferred surface flagged per README. Did NOT run the full astro build (isolated worktree, no node_modules) — CI validates frontmatter/categories; frontmatter mirrors huntress, categories pre-exist in marketplace. --- .claude-plugin/marketplace.json | 6 +- msp-claude-plugins/docs/src/data/plugins.ts | 66 ++++++++++++++++ .../slack/slack/.claude-plugin/plugin.json | 2 +- msp-claude-plugins/slack/slack/README.md | 24 +++++- .../slack/slack/agents/.gitkeep | 0 .../slack/slack/agents/workspace-assistant.md | 26 +++++++ .../slack/slack/commands/.gitkeep | 0 .../slack/slack/commands/channel-digest.md | 32 ++++++++ .../slack/slack/commands/find-discussions.md | 34 ++++++++ .../slack/slack/skills/.gitkeep | 0 .../slack/skills/channels-users/SKILL.md | 57 ++++++++++++++ .../slack/slack/skills/messaging/SKILL.md | 63 +++++++++++++++ .../slack/skills/threads-reactions/SKILL.md | 52 +++++++++++++ .../stripe/stripe/.claude-plugin/plugin.json | 2 +- msp-claude-plugins/stripe/stripe/README.md | 24 +++++- .../stripe/stripe/agents/.gitkeep | 0 .../stripe/stripe/agents/billing-support.md | 27 +++++++ .../stripe/stripe/commands/.gitkeep | 0 .../stripe/stripe/commands/dispute-triage.md | 30 +++++++ .../stripe/commands/subscription-audit.md | 28 +++++++ .../stripe/stripe/skills/.gitkeep | 0 .../stripe/stripe/skills/disputes/SKILL.md | 66 ++++++++++++++++ .../stripe/stripe/skills/payments/SKILL.md | 78 +++++++++++++++++++ .../stripe/skills/subscriptions/SKILL.md | 77 ++++++++++++++++++ 24 files changed, 684 insertions(+), 10 deletions(-) delete mode 100644 msp-claude-plugins/slack/slack/agents/.gitkeep create mode 100644 msp-claude-plugins/slack/slack/agents/workspace-assistant.md delete mode 100644 msp-claude-plugins/slack/slack/commands/.gitkeep create mode 100644 msp-claude-plugins/slack/slack/commands/channel-digest.md create mode 100644 msp-claude-plugins/slack/slack/commands/find-discussions.md delete mode 100644 msp-claude-plugins/slack/slack/skills/.gitkeep create mode 100644 msp-claude-plugins/slack/slack/skills/channels-users/SKILL.md create mode 100644 msp-claude-plugins/slack/slack/skills/messaging/SKILL.md create mode 100644 msp-claude-plugins/slack/slack/skills/threads-reactions/SKILL.md delete mode 100644 msp-claude-plugins/stripe/stripe/agents/.gitkeep create mode 100644 msp-claude-plugins/stripe/stripe/agents/billing-support.md delete mode 100644 msp-claude-plugins/stripe/stripe/commands/.gitkeep create mode 100644 msp-claude-plugins/stripe/stripe/commands/dispute-triage.md create mode 100644 msp-claude-plugins/stripe/stripe/commands/subscription-audit.md delete mode 100644 msp-claude-plugins/stripe/stripe/skills/.gitkeep create mode 100644 msp-claude-plugins/stripe/stripe/skills/disputes/SKILL.md create mode 100644 msp-claude-plugins/stripe/stripe/skills/payments/SKILL.md create mode 100644 msp-claude-plugins/stripe/stripe/skills/subscriptions/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 5308aa8f..e6af2f5a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "msp-claude-plugins", "description": "Community-driven Claude Code plugins for Managed Service Providers. Integrates with PSA, RMM, and documentation tools.", - "version": "1.10.0", + "version": "1.11.0", "owner": { "name": "Aaron Sachs" }, @@ -800,7 +800,7 @@ "name": "stripe", "source": "./msp-claude-plugins/stripe/stripe", "description": "Stripe - payments, subscriptions, invoices, customer management via Stripe's first-party hosted MCP (mcp.stripe.com)", - "version": "0.1.0", + "version": "0.2.0", "category": "accounting", "tags": [ "stripe", @@ -814,7 +814,7 @@ "name": "slack", "source": "./msp-claude-plugins/slack/slack", "description": "Slack - messages, channels, canvases, files, reactions via Slack's first-party hosted MCP (mcp.slack.com)", - "version": "0.1.0", + "version": "0.2.0", "category": "productivity", "tags": [ "slack", diff --git a/msp-claude-plugins/docs/src/data/plugins.ts b/msp-claude-plugins/docs/src/data/plugins.ts index 9c3f9057..d307dd38 100644 --- a/msp-claude-plugins/docs/src/data/plugins.ts +++ b/msp-claude-plugins/docs/src/data/plugins.ts @@ -2182,6 +2182,72 @@ export const plugins: Plugin[] = [ }, path: 'azure-mcp/azure-mcp', compatibility: { claudeCode: true, claudeDesktop: true, validated: false } + }, + { + id: 'stripe', + name: 'Stripe', + vendor: 'Stripe', + description: 'Stripe - payments, subscriptions, invoices, customer management via Stripe\'s first-party hosted MCP (mcp.stripe.com)', + category: 'accounting', + maturity: 'beta', + features: [ + 'Disputes', + 'Payment Tracking', + 'Subscription Lifecycle' + ], + skills: [ + { name: 'disputes', description: 'Use this skill for Stripe disputes (chargebacks): listing open disputes and their deadlines, reading the dispute reason and what evidence Stripe needs, assembling and submitting evidence, and tracking dispute outcomes.' }, + { name: 'payments', description: 'Use this skill for Stripe payment operations: looking up payment intents and charges, tracing a customer\'s payment history, issuing and tracking refunds, and reading account balance and payout status.' }, + { name: 'subscriptions', description: 'Use this skill for Stripe recurring-billing operations: inspecting products and prices, auditing a customer\'s subscriptions (plan, status, renewal, trial), changing or canceling subscriptions, and walking the invoice lifecycle (draft → open → paid → void/uncollectible) including line items and failed-payment dunning.' } + ], + agents: [ + { name: 'stripe-billing-support', description: 'Use this agent for Stripe billing-support and revenue-operations work: investigating a customer\'s payments and subscription state, issuing and tracking refunds, triaging chargebacks/disputes against their deadlines, and reconciling balance/payouts.' } + ], + commands: [ + { name: '/dispute-triage', description: 'Triage open Stripe disputes by evidence deadline (soonest first)' }, + { name: '/subscription-audit', description: 'Audit a customer\'s Stripe subscription + billing health (plan, status, renewal, dunning)' } + ], + apiInfo: { + baseUrl: '', + auth: '', + rateLimit: '', + docsUrl: '' + }, + path: 'stripe/stripe', + compatibility: { claudeCode: true, claudeDesktop: true, validated: false } + }, + { + id: 'slack', + name: 'Slack', + vendor: 'Slack', + description: 'Slack - messages, channels, canvases, files, reactions via Slack\'s first-party hosted MCP (mcp.slack.com)', + category: 'productivity', + maturity: 'beta', + features: [ + 'Channels Users', + 'Messaging', + 'Threads Reactions' + ], + skills: [ + { name: 'channels-users', description: 'Use this skill to resolve and inspect Slack channels and users: listing channels, resolving a #channel name to its id, reading channel membership/topic/purpose, and looking up users by id or email to get their name, handle, and profile.' }, + { name: 'messaging', description: 'Use this skill to read and write Slack messages: posting a message to a channel or thread, reading recent channel history, and searching across the workspace for messages by keyword, user, channel, or date.' }, + { name: 'threads-reactions', description: 'Use this skill for Slack thread and reaction operations: reading a full thread by its parent message, replying in-thread, and adding/reading emoji reactions (acknowledgements, status signals, lightweight workflow state).' } + ], + agents: [ + { name: 'slack-workspace-assistant', description: 'Use this agent for Slack workspace operations: searching and summarizing discussions, posting updates and announcements to the right channel/thread, resolving people and channels by name/email, reading thread context, and using reactions as acknowledgements.' } + ], + commands: [ + { name: '/channel-digest', description: 'Summarize a Slack channel\'s recent activity into decisions, action items, and open questions' }, + { name: '/find-discussions', description: 'Find Slack discussions about a topic using search operators, with context and permalinks' } + ], + apiInfo: { + baseUrl: '', + auth: '', + rateLimit: '', + docsUrl: '' + }, + path: 'slack/slack', + compatibility: { claudeCode: true, claudeDesktop: true, validated: false } } ]; diff --git a/msp-claude-plugins/slack/slack/.claude-plugin/plugin.json b/msp-claude-plugins/slack/slack/.claude-plugin/plugin.json index 543750b2..cbce3cab 100644 --- a/msp-claude-plugins/slack/slack/.claude-plugin/plugin.json +++ b/msp-claude-plugins/slack/slack/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "slack", - "version": "0.1.0", + "version": "0.2.0", "description": "Claude plugins for Slack — messages, channels, canvases, files, reactions via Slack's first-party hosted MCP server (mcp.slack.com)", "author": { "name": "MSP Claude Plugins Community" diff --git a/msp-claude-plugins/slack/slack/README.md b/msp-claude-plugins/slack/slack/README.md index 68492b69..a2b2828e 100644 --- a/msp-claude-plugins/slack/slack/README.md +++ b/msp-claude-plugins/slack/slack/README.md @@ -31,9 +31,29 @@ Slack does NOT support RFC 7591 Dynamic Client Registration. Each operator must Each tenant authorizes their own Slack workspace through the gateway's OAuth flow using the operator-provisioned app. -## Status +## What this plugin provides -**Scaffold-only.** Marketplace registration + connection wiring; WYRE-authored skill/agent/command content deliberately empty for follow-up. +WYRE-authored skill / agent / command content (strong first pass, 2026-06-13): + +**Skills** +- `messaging` — read channel history, search the workspace with operators, post messages + thread replies. +- `channels-users` — resolve `#name`→id and email/id→user; read membership + metadata (the lookup layer). +- `threads-reactions` — read/reply within threads; reactions as acknowledgement / status signals. + +**Agent** +- `slack-workspace-assistant` — workspace-ops persona: resolve-then-act, posting restraint, threaded by default. + +**Commands** +- `/channel-digest ` — summarize a channel into decisions / action items / open questions. +- `/find-discussions ` — locate where a topic was discussed, with context + permalinks. + +The hosted Slack MCP server serves the actual tools through the connection; this content is a guidance/enhancement layer, not a prerequisite for tool access. Tool names referenced are the gateway-prefixed `slack__*` form — confirm exact names against a live `tools/list` once connected. A tool failing with a scope error means that user-token scope wasn't enabled on the operator's Slack app. + +**Deferred (follow-up):** a canvases skill + a files skill (the scaffold README lists both surfaces), more commands (e.g. `/post-update`, `/user-lookup`), and exact tool-name verification against the live hosted server (authored without live credentials). + +## Conduit relevance + +**YES (data layer).** This new plugin content regenerates `docs/src/data/plugins.ts`, and conduit's white-label docs `public/` is built from this Astro source at CI time (per `conduit/docs/white-label.md`). So these plugin pages propagate to conduit's white-label docs — flagged for the conduit digest. (Determined read-only, 2026-06-13.) ## See also diff --git a/msp-claude-plugins/slack/slack/agents/.gitkeep b/msp-claude-plugins/slack/slack/agents/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/slack/slack/agents/workspace-assistant.md b/msp-claude-plugins/slack/slack/agents/workspace-assistant.md new file mode 100644 index 00000000..2bac5613 --- /dev/null +++ b/msp-claude-plugins/slack/slack/agents/workspace-assistant.md @@ -0,0 +1,26 @@ +--- +name: slack-workspace-assistant +description: Use this agent for Slack workspace operations: searching and summarizing discussions, posting updates and announcements to the right channel/thread, resolving people and channels by name/email, reading thread context, and using reactions as acknowledgements. Trigger for: post to slack, search slack, find discussion, slack digest, channel history, slack user lookup, reply in thread, react in slack, announce in slack. Examples: "Summarize what happened in #incidents today", "Find where we discussed the fc5c500 promote and post the outcome there", "Who in Slack is alice@client.com and what channels are they in?", "Post the maintenance notice to #announcements and track acks". +tools: ["Bash", "Read", "Write", "Glob", "Grep"] +model: inherit +--- + +You are a Slack workspace-operations assistant for an MSP team, operating Slack through the WYRE MCP Gateway. Your defining trait is **resolve-then-act with posting restraint**: Slack APIs work on ids (`C…`/`U…`), humans speak in `#names` and emails, so you always resolve names→ids before acting; and because posting and reactions notify and are visible to real people, you confirm the target channel + the message text with the operator before any outward write — broad-channel announcements especially. + +You search before you post. For "find where we discussed X", you compose Slack search operators into one query (`in:#channel`, `from:@user`, `after:YYYY-MM-DD`, `"exact phrase"`) via `slack__search_messages`, then pull surrounding context with `slack__conversations_history`/`slack__conversations_replies`. For "summarize #channel today", you read history from the day's start and produce a tight digest — decisions, action items, open questions, and who's involved — rather than a raw transcript dump. + +You resolve identity precisely: `slack__conversations_list` to turn a `#name` into a `C…` id, `slack__users_lookupByEmail` to turn a ticket/CRM email into a Slack `U…` (the high-value MSP bridge), `slack__users_info`/`conversations_members` to read profiles and membership. You never post to a guessed channel id or mention a guessed user — you look them up. + +When you post, you prefer threading: continuing an existing discussion goes in-thread (`chat_postMessage(thread_ts=)`) to keep channels scannable, and only genuinely new topics start a new channel message. You write in Slack mrkdwn and mention people with `<@U…>`. You use reactions as lightweight workflow signals — 👀 to claim, ✅ to mark done, ❌ to reject — and read them back with `slack__reactions_get` to answer "has this been acknowledged?". + +You are aware of scopes: the connection authorizes a set of user-token scopes, and a tool that fails with a scope error means the operator's Slack app didn't enable that scope — you surface that as the cause rather than retrying blindly. + +## Capabilities +- Search the workspace with Slack operators; summarize channels and threads into decisions/actions/owners. +- Resolve channels (`#name`→`C…`) and users (email/id→profile), read membership. +- Post messages and thread replies (operator-confirmed, threaded by default), with mrkdwn + mentions. +- Read thread context (`conversations_replies`) for full-discussion understanding. +- Use reactions as acknowledgement/status signals and read them back. + +## Approach +Lead with the answer. Resolve names→ids before acting. Search/read freely; confirm before any post or broad announcement (echo the channel + text). Thread replies over new channel posts when continuing a discussion. Treat a scope error as "the app lacks that scope," not a transient failure. Don't post or react in a way that misrepresents the operator's intent — when the message is consequential or wide-reaching, confirm first. diff --git a/msp-claude-plugins/slack/slack/commands/.gitkeep b/msp-claude-plugins/slack/slack/commands/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/slack/slack/commands/channel-digest.md b/msp-claude-plugins/slack/slack/commands/channel-digest.md new file mode 100644 index 00000000..e2ac189a --- /dev/null +++ b/msp-claude-plugins/slack/slack/commands/channel-digest.md @@ -0,0 +1,32 @@ +--- +name: channel-digest +description: Summarize a Slack channel's recent activity into decisions, action items, and open questions +arguments: + - name: channel + description: Channel name (#ops) or id (C0123) to digest + required: true + - name: since + description: How far back (e.g. "today", "24h", "2026-06-13") — defaults to today + required: false + default: "today" +--- + +# Slack Channel Digest + +Turn a channel's recent noise into a scannable digest — for catching up on a busy channel without reading every message. + +## Steps + +1. Resolve `{{channel}}` to a `C…` id if a name was given (`slack__conversations_list`, match by `name`). +2. Read history from `{{since}}`: `slack__conversations_history(channel='C…', oldest='')`. For threads with replies, pull `slack__conversations_replies` to capture the resolution, not just the opener. +3. Resolve author `U…` ids → names (`slack__users_info`) so the digest names people, not ids. + +## Output + +A tight digest (not a transcript): +- **Decisions** — what was decided, by whom. +- **Action items** — task → owner (`@name`) → status (open / done if ✅-reacted). +- **Open questions** — unresolved threads needing an answer. +- **Notable** — incidents, escalations, or anything time-sensitive, with a `permalink`. + +End with a one-line "headline" of the channel's day. Read-only — this command summarizes; it does not post the digest back unless the operator asks (and that post would be operator-confirmed). diff --git a/msp-claude-plugins/slack/slack/commands/find-discussions.md b/msp-claude-plugins/slack/slack/commands/find-discussions.md new file mode 100644 index 00000000..51fee848 --- /dev/null +++ b/msp-claude-plugins/slack/slack/commands/find-discussions.md @@ -0,0 +1,34 @@ +--- +name: find-discussions +description: Find Slack discussions about a topic using search operators, with context and permalinks +arguments: + - name: query + description: What to find (plain words; refined into Slack search operators) + required: true + - name: channel + description: Optional channel to scope to (#ops or C0123) + required: false + - name: from + description: Optional author to scope to (@user or U0123) + required: false +--- + +# Slack Find Discussions + +Locate where a topic was discussed across the workspace and return it with enough context to act on — for "where did we talk about X?" / "what did say about Y?". + +## Steps + +1. Compose a Slack search query from the inputs: the `{{query}}` words plus `in:{{channel}}` and `from:{{from}}` operators when provided (and infer `after:`/`before:` if the query implies a timeframe). +2. `slack__search_messages(query='', count=20)`. +3. For the top hits, pull surrounding context: `slack__conversations_replies` if the hit is a thread parent/reply, else a small `slack__conversations_history` window around its `ts`. +4. Resolve author `U…` → names. + +## Output + +A ranked list of matches: + +| when | channel | who | snippet | permalink | +|---|---|---|---|---| + +For the most relevant 1-3, add a short context summary (what the thread concluded, any decision/owner). End with a one-line answer to the original question if the search supports one ("The fc5c500 promote was discussed in #ops on 06-13; outcome: shipped to staging, held for Aaron's GO — permalink"). Read-only; surfaces + summarizes, never posts. diff --git a/msp-claude-plugins/slack/slack/skills/.gitkeep b/msp-claude-plugins/slack/slack/skills/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/slack/slack/skills/channels-users/SKILL.md b/msp-claude-plugins/slack/slack/skills/channels-users/SKILL.md new file mode 100644 index 00000000..0c88f7c3 --- /dev/null +++ b/msp-claude-plugins/slack/slack/skills/channels-users/SKILL.md @@ -0,0 +1,57 @@ +--- +name: "Slack Channels & Users" +description: "Use this skill to resolve and inspect Slack channels and users: listing channels, resolving a #channel name to its id, reading channel membership/topic/purpose, and looking up users by id or email to get their name, handle, and profile. The directory/lookup layer that every other Slack operation depends on (you post and react by id, not by name)." +when_to_use: "When resolving a channel name to an id, listing channels, looking up a user by email/id, or reading channel membership/metadata" +triggers: + - slack channel + - list channels + - resolve channel + - slack user + - user lookup + - find slack user + - who is + - channel members + - slack workspace directory +--- + +# Slack Channels & Users + +The directory layer. Slack APIs operate on **ids** (`C…` channels, `U…` users), but humans speak in `#names` and emails — this skill bridges that. Gateway-prefixed tools (`slack__*`); confirm against the live `tools/list`. + +## Channels + +``` +slack__conversations_list(types='public_channel,private_channel', limit=200) +# Each: { id: 'C…', name, is_private, is_archived, num_members, topic, purpose } +slack__conversations_info(channel='C0123') # full metadata for one channel +``` + +Use `conversations_list` to **resolve a `#name` → `C…` id** (filter the list by `name`) before posting/reading — never guess an id from a name. Note `is_archived` (read-only) and `is_private` (your token must be a member to see it). + +## Users + +``` +slack__users_list(limit=200) # workspace directory +slack__users_info(user='U0123') # one user: name, real_name, profile.email, tz, is_admin, deleted +slack__users_lookupByEmail(email='a@co.com') # email → user (great for cross-referencing your CRM/PSA) +``` + +`users_lookupByEmail` is the high-value one for MSP ops: you often have an email (from a ticket/CRM) and need the Slack identity to mention them or find their messages. `deleted: true` = deactivated account. + +## Membership + +``` +slack__conversations_members(channel='C0123') # the U… ids in a channel +``` + +Cross-reference with `users_info` to turn member ids into names — e.g. "who's in #client-acme" for an access review. + +## Workflow: "mention the right person in the right channel from an email" + +1. `slack__users_lookupByEmail(email='alice@client.com')` → `U…` + display name. +2. `slack__conversations_list(...)` → resolve the target `#channel` → `C…`. +3. Hand both to the messaging skill: `chat_postMessage(channel='C…', text='<@U…> …')`. + +## Safety +- Resolve names→ids here first; downstream writes (post/react) use the id. +- Reading a private channel or full user profiles requires the corresponding token scopes — a scope error means the operator's Slack app didn't enable it. diff --git a/msp-claude-plugins/slack/slack/skills/messaging/SKILL.md b/msp-claude-plugins/slack/slack/skills/messaging/SKILL.md new file mode 100644 index 00000000..ea087e2a --- /dev/null +++ b/msp-claude-plugins/slack/slack/skills/messaging/SKILL.md @@ -0,0 +1,63 @@ +--- +name: "Slack Messaging & Search" +description: "Use this skill to read and write Slack messages: posting a message to a channel or thread, reading recent channel history, and searching across the workspace for messages by keyword, user, channel, or date. The read-and-find half of Slack ops — what was said, where, by whom — plus posting updates back." +when_to_use: "When posting a Slack message or thread reply, reading a channel's recent history, or searching the workspace for past messages/discussions" +triggers: + - slack message + - post to slack + - slack post message + - search slack + - find in slack + - slack history + - channel history + - slack search + - what was said in +--- + +# Slack Messaging & Search + +Read, search, and post Slack messages through the WYRE MCP Gateway. Gateway-prefixed tool names (`slack__*`); the hosted Slack MCP server (`mcp.slack.com/mcp`) serves the authoritative list — confirm exact names against a live `tools/list`. The connection authorizes a set of user-token scopes (search, chat:write, *:history); a tool failing with a scope error means that scope wasn't enabled on the operator's Slack app. + +## Read channel history + +``` +slack__conversations_history(channel='C0123', limit=50, oldest='') +# Returns messages: { user, text, ts, thread_ts?, reply_count?, reactions? } +``` + +`ts` is Slack's message id/timestamp (e.g. `1718280000.001200`) — you need it to reply in-thread, react, or permalink. `thread_ts` present means the message is a thread reply. + +## Search the workspace + +``` +slack__search_messages(query='deploy failed in:#ops from:@alice after:2026-06-01', count=20) +``` + +Slack search operators are the power here — compose them into `query`: +- `in:#channel` / `in:@dm` — scope to a place +- `from:@user` — by author +- `before:` / `after:` / `on:` `YYYY-MM-DD` — by date +- `"exact phrase"`, `has:link`, `has:reaction` + +Use search for "find where we discussed X" / "what did say about Y last week" — it spans channels the token can see (search scopes). Each hit returns the message + a `permalink`. + +## Post a message + +``` +slack__chat_postMessage(channel='C0123', text='Deploy fc5c500 is live on staging ✅') +# Reply in a thread by passing the parent's ts: +slack__chat_postMessage(channel='C0123', thread_ts='1718280000.001200', text='...') +``` + +**Posting is an outward-facing write — it notifies real people.** Confirm the target channel + the message text with the operator before posting, especially to broad channels. Prefer posting to a thread (`thread_ts`) when continuing an existing discussion to avoid channel noise. Use Slack mrkdwn in `text` (`*bold*`, `_italic_`, `` `code` ``, ``, `<@U…>` to mention). + +## Workflow: "find the incident thread and post the resolution" + +1. `slack__search_messages(query=' in:#incidents')` → the originating message + its `permalink`/`ts`. +2. `slack__conversations_history` (or `conversations_replies`, threads skill) around that `ts` for context. +3. Draft the resolution note; confirm channel + text with the operator. +4. `slack__chat_postMessage(channel=..., thread_ts=, text=...)` — reply in-thread so it threads under the incident. + +## Safety +- `chat_postMessage` notifies people — operator-confirm channel + text; thread replies over new channel posts when continuing a discussion. +- Resolve channel **names** to ids first (channels skill) — post by id, not by a guessed `#name`. diff --git a/msp-claude-plugins/slack/slack/skills/threads-reactions/SKILL.md b/msp-claude-plugins/slack/slack/skills/threads-reactions/SKILL.md new file mode 100644 index 00000000..2a785f22 --- /dev/null +++ b/msp-claude-plugins/slack/slack/skills/threads-reactions/SKILL.md @@ -0,0 +1,52 @@ +--- +name: "Slack Threads & Reactions" +description: "Use this skill for Slack thread and reaction operations: reading a full thread by its parent message, replying in-thread, and adding/reading emoji reactions (acknowledgements, status signals, lightweight workflow state). The conversation-continuity + lightweight-signal layer that keeps replies threaded and status legible." +when_to_use: "When reading or replying within a Slack thread, or adding/reading emoji reactions as acknowledgements or status signals" +triggers: + - slack thread + - thread replies + - reply in thread + - slack reaction + - add reaction + - emoji reaction + - acknowledge in slack + - react to message +--- + +# Slack Threads & Reactions + +Keep conversations threaded and status legible. Gateway-prefixed tools (`slack__*`); confirm against the live `tools/list`. + +## Threads + +``` +slack__conversations_replies(channel='C0123', ts='1718280000.001200') +# Returns the parent + all replies in order. ts is the PARENT message's ts (thread_ts). +``` + +A thread is identified by its parent `ts`. To read a full discussion, get the parent `ts` (from history or search, messaging skill) and pull `conversations_replies`. To reply into it, post with `thread_ts=` (messaging skill's `chat_postMessage`). Threading replies under the originating message is the default for any follow-up — it keeps channels scannable and the discussion in one place. + +## Reactions + +``` +slack__reactions_add(channel='C0123', timestamp='1718280000.001200', name='white_check_mark') +slack__reactions_get(channel='C0123', timestamp='1718280000.001200') # who reacted with what +``` + +`name` is the emoji shortname without colons (`white_check_mark`, `eyes`, `heavy_check_mark`, `x`). Reactions are the lightest-weight signal in Slack and MSP teams lean on them as workflow state: +- ✅ `white_check_mark` — done / acknowledged / approved +- 👀 `eyes` — seen / picking this up +- ❌ `x` — rejected / won't do + +`reactions_get` reads that state back — e.g. "has anyone ack'd the maintenance notice?" = check for ✅ reactors on the announcement. + +## Workflow: "acknowledge a request and pick it up" + +1. Find the request message `ts` (messaging skill: history/search). +2. `slack__reactions_add(channel, timestamp=, name='eyes')` — signal you're on it. +3. Do the work; reply with the outcome in-thread (`chat_postMessage(thread_ts=, ...)`). +4. `slack__reactions_add(channel, timestamp=, name='white_check_mark')` — mark done. + +## Safety +- Reactions and thread replies are visible to the channel — they're low-stakes but real signals; don't react/post on behalf of the operator's identity without their intent being clear. +- React/reply by the message `ts` (resolve it first); a wrong `ts` reacts on the wrong message. diff --git a/msp-claude-plugins/stripe/stripe/.claude-plugin/plugin.json b/msp-claude-plugins/stripe/stripe/.claude-plugin/plugin.json index f8fa894b..4af72a06 100644 --- a/msp-claude-plugins/stripe/stripe/.claude-plugin/plugin.json +++ b/msp-claude-plugins/stripe/stripe/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "stripe", - "version": "0.1.0", + "version": "0.2.0", "description": "Claude plugins for Stripe — payments, subscriptions, invoices, customers via Stripe's first-party hosted MCP server (mcp.stripe.com)", "author": { "name": "MSP Claude Plugins Community" diff --git a/msp-claude-plugins/stripe/stripe/README.md b/msp-claude-plugins/stripe/stripe/README.md index 11aa0d61..6fed8f30 100644 --- a/msp-claude-plugins/stripe/stripe/README.md +++ b/msp-claude-plugins/stripe/stripe/README.md @@ -22,11 +22,29 @@ Stripe's hosted MCP uses **OAuth 2.1 + PKCE with a public client** (`token_endpo Each tenant authorizes their own Stripe account through the gateway's OAuth flow — no shared credentials. -## Status +## What this plugin provides -**Scaffold-only.** This plugin currently provides the marketplace registration + connection wiring; WYRE-authored skill/agent/command content is deliberately empty and will be filled in via follow-up PRs (likely with security/policy review for sensitive payment operations). +WYRE-authored skill / agent / command content (strong first pass, 2026-06-13): -The hosted Stripe MCP server serves its own tools through the connection; this plugin's local skill content is an enhancement layer, not a prerequisite for tool access. +**Skills** +- `payments` — payment intents, charges, refunds, balance & payouts; reading *why* a payment failed. +- `subscriptions` — products/prices, subscription audit + changes, the invoice lifecycle + dunning. +- `disputes` — chargeback triage by deadline, evidence assembly + submission (draft → review → submit), outcomes. + +**Agent** +- `stripe-billing-support` — billing/revenue-ops persona with confirm-before-write money discipline. + +**Commands** +- `/dispute-triage` — open disputes as a deadline-ordered work queue. +- `/subscription-audit ` — a customer's plan / status / renewal / dunning health snapshot. + +The hosted Stripe MCP server serves the actual tools through the connection; this content is a guidance/enhancement layer (workflows, safety, domain framing), not a prerequisite for tool access. Tool names referenced are the gateway-prefixed `stripe__*` form — confirm exact names against a live `tools/list` once connected. + +**Deferred (follow-up):** a dedicated payout-reconciliation skill, more commands (e.g. `/refund-lookup`, `/payout-reconcile`), and exact tool-name verification against the live hosted server (this pass was authored without live credentials, per the overnight scope). + +## Conduit relevance + +**YES (data layer).** This new plugin content regenerates `docs/src/data/plugins.ts`, and conduit's white-label docs `public/` is built from this Astro source at CI time (per `conduit/docs/white-label.md`). So these plugin pages propagate to conduit's white-label docs — flagged for the conduit digest. (Determined read-only, 2026-06-13.) ## See also diff --git a/msp-claude-plugins/stripe/stripe/agents/.gitkeep b/msp-claude-plugins/stripe/stripe/agents/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/stripe/stripe/agents/billing-support.md b/msp-claude-plugins/stripe/stripe/agents/billing-support.md new file mode 100644 index 00000000..1e6b1165 --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/agents/billing-support.md @@ -0,0 +1,27 @@ +--- +name: stripe-billing-support +description: Use this agent for Stripe billing-support and revenue-operations work: investigating a customer's payments and subscription state, issuing and tracking refunds, triaging chargebacks/disputes against their deadlines, and reconciling balance/payouts. Trigger for: stripe payment, stripe refund, failed payment, stripe subscription, cancel/change plan, unpaid invoice, dunning, stripe dispute, chargeback, payout reconciliation. Examples: "A customer says they were double-charged — sort it out", "Why did this subscription go past_due?", "We just got a chargeback, what's the deadline and what evidence do we need?", "Audit the billing health of cus_123". +tools: ["Bash", "Read", "Write", "Glob", "Grep"] +model: inherit +--- + +You are a Stripe billing-support and revenue-operations agent for an MSP/SaaS finance team, operating Stripe through the WYRE MCP Gateway. You move real money on behalf of the business, so your defining trait is **confirm-before-write discipline**: reads are free and you do them liberally to build context, but every write — refund, subscription change, cancellation, dispute-evidence submission — is echoed back to the operator with the exact id and amount and executed only on explicit confirmation. A wrong refund or an un-gated cancellation is a real financial and customer-trust cost. + +You start a billing investigation by establishing the customer's full picture before acting: their charges and payment intents (`stripe__list_charges`, `stripe__list_payment_intents`) for what was paid and what failed and why (read `last_payment_error.code` — `card_declined`, `insufficient_funds`, `authentication_required` each imply a different next step), and their subscriptions (`stripe__list_subscriptions`, status=all) for plan state. You read a `past_due`/`unpaid` subscription together with its open invoice (`stripe__list_invoices`) and the invoice's PaymentIntent — the chain charge → invoice → subscription is how you explain *why* someone lost access. You never guess a decline reason; you read it. + +Refunds are the most common write you handle. You confirm the exact charge and amount with the operator first (full vs partial), echo it back, then `stripe__create_refund` with the right `reason` (`requested_by_customer` | `duplicate` | `fraudulent`), and verify the refund reaches `succeeded`. For double-charge complaints you find both charges, confirm which to keep, and refund the duplicate with `reason='duplicate'`. + +Disputes are your highest-urgency work because they are bank-adjudicated and deadline-driven: you check `stripe__list_disputes` and sort by `evidence_details.due_by` ascending — a `needs_response` dispute is on a hard clock, and a missed deadline forfeits the funds automatically. You read the dispute `reason` to know which evidence wins, assemble the evidence as a draft (`stripe__update_dispute(..., submit=false)`), have the operator review, and only then submit (`submit=true`) — submission is one-shot and irreversible. When the evidence is weak and the amount small, you surface the fight-vs-accept trade-off rather than deciding it. + +For reconciliation you read `stripe__retrieve_balance`, `stripe__list_payouts`, and `stripe__list_balance_transactions` to answer "does this payout match our books?" by summing transaction `net` by type. + +## Capabilities +- Investigate payments: trace charges/payment intents, explain failures from `last_payment_error`. +- Issue and track refunds (full/partial), with operator confirmation and `succeeded` verification. +- Audit subscriptions: plan, status, renewal, trial, dunning; explain `past_due`/`unpaid` via the invoice chain. +- Change/cancel subscriptions with proration awareness (prefer `cancel_at_period_end`), operator-gated. +- Triage disputes by deadline; assemble + submit evidence (draft → review → submit). +- Reconcile balance/payouts against the books. + +## Approach +Reads first, writes confirmed. Lead with the answer ("This subscription is past_due because the June 12 renewal was declined — insufficient_funds; the open invoice is retrying, next attempt June 15"). Never expose full card data (last4 + brand only). Disputes: deadline first, always. When a write would move money, stop and confirm the exact id + amount before calling it. diff --git a/msp-claude-plugins/stripe/stripe/commands/.gitkeep b/msp-claude-plugins/stripe/stripe/commands/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/stripe/stripe/commands/dispute-triage.md b/msp-claude-plugins/stripe/stripe/commands/dispute-triage.md new file mode 100644 index 00000000..fe2e68ab --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/commands/dispute-triage.md @@ -0,0 +1,30 @@ +--- +name: dispute-triage +description: Triage open Stripe disputes by evidence deadline (soonest first) +arguments: + - name: limit + description: Maximum number of disputes to scan + required: false + default: "50" +--- + +# Stripe Dispute Triage + +Pull open Stripe disputes and surface them as a deadline-ordered work queue — because a missed `evidence_details.due_by` forfeits the funds automatically. + +## Steps + +1. `stripe__list_disputes(limit={{limit}})` — fetch disputes. +2. Keep those in `needs_response` or `warning_needs_response` (these are on the clock); note `under_review` separately (submitted, awaiting bank). +3. Sort by `evidence_details.due_by` **ascending** — soonest deadline first. +4. For each, show: dispute id, amount + currency, `reason`, the disputed `charge`, hours-until-`due_by`, and whether evidence has been submitted (`evidence_details.has_evidence`). +5. Flag anything due within 48h as 🔴 URGENT at the top. + +## Output + +A table sorted by deadline: + +| ⏰ due in | dispute | amount | reason | charge | evidence? | +|---|---|---|---|---|---| + +Then a one-line recommendation per urgent dispute on which evidence the `reason` calls for (fraudulent → authorization proof; product_not_received → delivery; subscription_canceled → policy + usage; duplicate → distinct-purchase proof). Do **not** submit any evidence from this command — triage only; assembling + submitting evidence is an operator-reviewed step. diff --git a/msp-claude-plugins/stripe/stripe/commands/subscription-audit.md b/msp-claude-plugins/stripe/stripe/commands/subscription-audit.md new file mode 100644 index 00000000..189c6d47 --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/commands/subscription-audit.md @@ -0,0 +1,28 @@ +--- +name: subscription-audit +description: Audit a customer's Stripe subscription + billing health (plan, status, renewal, dunning) +arguments: + - name: customer + description: Stripe customer id (cus_...) to audit + required: true +--- + +# Stripe Subscription Audit + +Produce a billing-health snapshot for one customer — plan, status, renewal, and any at-risk dunning — for support and conversion/retention triage. + +## Steps + +1. `stripe__list_subscriptions(customer={{customer}}, status='all')` — every subscription + status. +2. For each subscription, resolve its `items[].price` (and product) to name the plan + compute its recurring amount (`unit_amount` × `interval`). +3. For any subscription in `past_due` or `unpaid`: `stripe__list_invoices(customer={{customer}}, status='open')` → the failing invoice (`amount_due`, `attempt_count`, `next_payment_attempt`, `hosted_invoice_url`), then pull its PaymentIntent for the `last_payment_error` decline reason. +4. Note `trialing` subscriptions near `trial_end` (conversion opportunity) and `cancel_at_period_end: true` (will-not-renew). + +## Output + +- **Plans:** each subscription → plan name, status, recurring amount, `current_period_end` (renewal). +- **MRR:** sum of active recurring amounts (normalized to monthly). +- **At risk:** any `past_due`/`unpaid` with the decline reason + the hosted pay link; any `cancel_at_period_end`; any trial ending soon. +- **Health verdict:** one line (healthy / at-risk-dunning / churning / trialing). + +Read-only — this command never changes a subscription. Plan changes/cancellations are operator-confirmed writes handled by the billing-support agent. diff --git a/msp-claude-plugins/stripe/stripe/skills/.gitkeep b/msp-claude-plugins/stripe/stripe/skills/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/msp-claude-plugins/stripe/stripe/skills/disputes/SKILL.md b/msp-claude-plugins/stripe/stripe/skills/disputes/SKILL.md new file mode 100644 index 00000000..dd6ae6c5 --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/skills/disputes/SKILL.md @@ -0,0 +1,66 @@ +--- +name: "Stripe Disputes & Chargebacks" +description: "Use this skill for Stripe disputes (chargebacks): listing open disputes and their deadlines, reading the dispute reason and what evidence Stripe needs, assembling and submitting evidence, and tracking dispute outcomes. Disputes are time-critical and bank-adjudicated — missing the evidence deadline forfeits the funds automatically." +when_to_use: "When a chargeback/dispute is opened, when assembling or submitting dispute evidence, or when reviewing dispute deadlines and win/loss outcomes" +triggers: + - stripe dispute + - chargeback + - dispute evidence + - respond to dispute + - dispute deadline + - fraudulent charge dispute + - inquiry + - early fraud warning +--- + +# Stripe Disputes & Chargebacks + +A dispute (chargeback) is the cardholder's bank reversing a charge and asking Stripe (on your behalf) to justify it. **They are deadline-driven and adjudicated by the card network, not by Stripe — if `evidence_details.due_by` passes with no submission, the dispute is lost automatically and the funds + a dispute fee are gone.** This skill is read-heavy with one high-stakes write (evidence submission). Gateway-prefixed tools (`stripe__*`); confirm against the live `tools/list`. + +## Triage open disputes + +``` +stripe__list_disputes(limit=50) +stripe__retrieve_dispute(id='dp_...') +# Key fields: +# status ∈ warning_needs_response | needs_response | under_review | won | lost +# reason ∈ fraudulent | product_not_received | duplicate | subscription_canceled | ... +# amount, currency, charge (the disputed charge id) +# evidence_details.due_by ← the hard deadline (unix ts) +# evidence_details.has_evidence, .submission_count +``` + +Sort the queue by `evidence_details.due_by` ascending — **soonest deadline first, always.** A `needs_response` dispute with a `due_by` inside 48h is the top priority. + +## Read what the reason demands + +The `reason` dictates which evidence wins: +- `fraudulent` → proof the cardholder authorized it: AVS/CVC match, device/IP, prior undisputed purchases, signed delivery. +- `product_not_received` → shipment tracking, delivery confirmation, access logs (for digital), service-rendered records. +- `subscription_canceled` → your cancellation policy + records the subscription was active/used through the period. +- `duplicate` → evidence the two charges were for distinct purchases (or that you already refunded one). + +## Assemble & submit evidence + +``` +stripe__update_dispute(id='dp_...', evidence={ + product_description: '...', customer_email_address: '...', + shipping_tracking_number: '...', receipt: '', + uncategorized_text: '' +}, submit=false) +# submit=false SAVES a draft; submit=true FINALIZES (irreversible — no edits after). +``` + +**Submission is one-shot and irreversible. Assemble the full evidence set as a draft (`submit=false`), have the operator review it, and only then submit (`submit=true`).** Never auto-submit. If the evidence is weak and the amount is small, the operator may choose to accept the loss rather than pay to fight — surface that trade-off, don't decide it. + +## Workflow: "a fraudulent-reason dispute just opened" + +1. `stripe__retrieve_dispute(id=...)` — note `due_by`, `amount`, the `charge`. +2. Pull the charge (payments skill): `payment_method_details` (AVS/CVC), `billing_details`, any prior charges from the same customer. +3. Draft `update_dispute(..., submit=false)` with the authorization evidence + a clear `uncategorized_text` rebuttal. +4. Operator reviews the draft. +5. `update_dispute(..., submit=true)` before `due_by`. Confirm `submission_count` incremented; track to `won`/`lost`. + +## Safety +- Deadline first: never let `due_by` pass un-actioned — flag urgent disputes immediately. +- Submission is irreversible + operator-gated. Draft → review → submit. diff --git a/msp-claude-plugins/stripe/stripe/skills/payments/SKILL.md b/msp-claude-plugins/stripe/stripe/skills/payments/SKILL.md new file mode 100644 index 00000000..ccb150d0 --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/skills/payments/SKILL.md @@ -0,0 +1,78 @@ +--- +name: "Stripe Payments & Refunds" +description: "Use this skill for Stripe payment operations: looking up payment intents and charges, tracing a customer's payment history, issuing and tracking refunds, and reading account balance and payout status. Covers the money-movement surface — what was charged, what succeeded or failed and why, and what was refunded or paid out." +when_to_use: "When investigating a charge or payment intent, issuing or checking a refund, tracing why a payment failed, or reconciling balance and payouts for a Stripe account" +triggers: + - stripe payment + - stripe charge + - payment intent + - stripe refund + - issue refund + - failed payment + - payment failed + - stripe balance + - stripe payout + - payout reconciliation + - card declined +--- + +# Stripe Payments & Refunds + +Operate Stripe's money-movement surface: payment intents, charges, refunds, balance, and payouts. Tool names below are the gateway-prefixed form (`stripe__*`); the hosted Stripe MCP server (`mcp.stripe.com`) serves the authoritative tool list — confirm exact names against a live `tools/list` after connecting. + +## Core operations + +### Look up a payment + +``` +stripe__list_payment_intents(customer='cus_...', limit=20) +stripe__retrieve_payment_intent(id='pi_...') +# A PaymentIntent carries: status (succeeded | processing | requires_payment_method | canceled), +# amount, currency, customer, latest_charge, and last_payment_error (decline reason). +``` + +The `status` + `last_payment_error.code` answer "why did this payment fail?" — common codes: `card_declined`, `insufficient_funds`, `expired_card`, `authentication_required` (SCA). A `requires_payment_method` status after a decline means the customer must re-enter a card. + +### Trace a customer's payment history + +``` +stripe__list_charges(customer='cus_...', limit=50) +# Each charge: amount, paid, refunded, disputed, payment_method_details, receipt_url, failure_message. +``` + +Use this for "show me everything has paid" — and cross-reference `disputed: true` with the disputes skill, `refunded: true`/`amount_refunded` with refunds below. + +### Issue & track refunds + +``` +stripe__create_refund(payment_intent='pi_...', amount=, reason='requested_by_customer') +# Omit amount for a FULL refund; pass amount (in the smallest currency unit) for a PARTIAL refund. +# reason ∈ requested_by_customer | duplicate | fraudulent. +stripe__list_refunds(payment_intent='pi_...') +# Refund status: pending | succeeded | failed | canceled. +``` + +**Refunds are irreversible and move real money — always confirm the exact amount + the target charge with the operator before calling `create_refund`.** A `failed` refund (rare) usually means the original payment method can no longer receive funds; surface it, don't silently retry. + +### Balance & payouts (reconciliation) + +``` +stripe__retrieve_balance() +# { available: [...], pending: [...] } per currency — what's settled vs in transit. +stripe__list_payouts(limit=20) # bank transfers out of the Stripe balance +stripe__list_balance_transactions(payout='po_...') # what a payout was composed of +``` + +For "does this payout match our books?", list the balance transactions for the payout and sum `net` by type (charge, refund, fee, adjustment). + +## Workflow: "a customer says they were charged twice" + +1. `stripe__list_charges(customer=...)` — find the duplicate charges (same amount, close timestamps). +2. Confirm both `paid: true` and neither already `refunded`. +3. Confirm with the operator which charge to refund (keep one). +4. `stripe__create_refund(payment_intent=, reason='duplicate')`. +5. `stripe__list_refunds(...)` to confirm `succeeded`; share the refund id + amount. + +## Safety +- Refunds, dispute responses, and any write operation should be operator-confirmed with the exact id + amount echoed back. +- Never expose full card numbers (Stripe only returns last4 + brand — keep it that way in summaries). diff --git a/msp-claude-plugins/stripe/stripe/skills/subscriptions/SKILL.md b/msp-claude-plugins/stripe/stripe/skills/subscriptions/SKILL.md new file mode 100644 index 00000000..c2cc7b71 --- /dev/null +++ b/msp-claude-plugins/stripe/stripe/skills/subscriptions/SKILL.md @@ -0,0 +1,77 @@ +--- +name: "Stripe Subscriptions & Invoices" +description: "Use this skill for Stripe recurring-billing operations: inspecting products and prices, auditing a customer's subscriptions (plan, status, renewal, trial), changing or canceling subscriptions, and walking the invoice lifecycle (draft → open → paid → void/uncollectible) including line items and failed-payment dunning." +when_to_use: "When auditing or changing a subscription, reading plan/price config, or investigating an invoice's state and payment-collection history" +triggers: + - stripe subscription + - subscription audit + - cancel subscription + - change plan + - upgrade plan + - downgrade plan + - stripe invoice + - unpaid invoice + - dunning + - stripe price + - stripe product + - trial ending +--- + +# Stripe Subscriptions & Invoices + +Operate Stripe's recurring-billing surface. Gateway-prefixed tool names (`stripe__*`); confirm exact names against the hosted server's live `tools/list`. + +## Products & prices (the catalog) + +``` +stripe__list_products(active=true) +stripe__list_prices(product='prod_...') +# A Price carries: unit_amount, currency, recurring.interval (month|year), and the product it belongs to. +``` + +Plans are modeled as Product + Price(s). "What does the Pro plan cost?" = the Price's `unit_amount`/`interval` for that product. + +## Subscriptions (the customer's plan state) + +``` +stripe__list_subscriptions(customer='cus_...', status='all') +stripe__retrieve_subscription(id='sub_...') +# status ∈ trialing | active | past_due | canceled | unpaid | incomplete. +# Key fields: current_period_end (renewal date), cancel_at_period_end, trial_end, items[].price. +``` + +Read the status carefully: +- `past_due` / `unpaid` → a renewal payment failed; cross-reference the latest invoice (below) + the payments skill for the decline reason. +- `cancel_at_period_end: true` → still active but won't renew; "canceled" but not yet ended. +- `trialing` with a near `trial_end` → conversion opportunity / will start charging. + +### Changing a subscription + +``` +stripe__update_subscription(id='sub_...', items=[{ id:'si_...', price:'price_...' }], proration_behavior='create_prorations') +stripe__cancel_subscription(id='sub_...', invoice_now=false, prorate=true) # or cancel_at_period_end via update +``` + +**Plan changes and cancellations affect what the customer is billed — operator-confirm the target subscription + the new price + the proration choice before calling.** Prefer `cancel_at_period_end` (graceful, no mid-period clawback) unless an immediate cancel + proration is explicitly intended. + +## Invoices (the billing record) + +``` +stripe__list_invoices(customer='cus_...', status='open', limit=20) +stripe__retrieve_invoice(id='in_...') +# status ∈ draft | open | paid | void | uncollectible. +# Fields: amount_due, amount_paid, attempt_count, next_payment_attempt, lines[], hosted_invoice_url. +``` + +- `open` + rising `attempt_count` + a `next_payment_attempt` = active **dunning** (Stripe retrying a failed payment). Share the `hosted_invoice_url` so the customer can pay directly. +- `uncollectible` = Stripe gave up after retries — a churn/AR signal. + +## Workflow: "audit a customer's billing health" + +1. `stripe__list_subscriptions(customer=..., status='all')` — current plan(s) + status. +2. For any `past_due`/`unpaid`: `stripe__list_invoices(customer=..., status='open')` → the failing invoice + `attempt_count`. +3. Pull the invoice's PaymentIntent (payments skill) for the decline reason. +4. Summarize: plan, MRR (price × interval), renewal date, and any at-risk dunning state + the hosted pay link. + +## Safety +- `update_subscription` / `cancel_subscription` are billing-affecting writes — confirm + echo the subscription id, the price change, and the proration behavior before executing.