@capivv/mcp-server 0.1.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/client.d.ts +62 -3
- package/dist/client.js +220 -5
- package/dist/config.js +1 -1
- package/dist/http.d.ts +12 -0
- package/dist/http.js +102 -0
- package/dist/resources/guides.d.ts +2 -0
- package/dist/resources/guides.js +81 -0
- package/dist/resources/index.js +4 -0
- package/dist/resources/quickstart.d.ts +2 -0
- package/dist/resources/quickstart.js +173 -0
- package/dist/resources/rules.js +8 -2
- package/dist/tools/activate-rule.d.ts +3 -0
- package/dist/tools/activate-rule.js +9 -0
- package/dist/tools/api-key-usage.d.ts +3 -0
- package/dist/tools/api-key-usage.js +70 -0
- package/dist/tools/apply-rule.js +54 -13
- package/dist/tools/approve-change-request.d.ts +3 -0
- package/dist/tools/approve-change-request.js +16 -0
- package/dist/tools/archive-app.d.ts +3 -0
- package/dist/tools/archive-app.js +9 -0
- package/dist/tools/autopilot-status.d.ts +3 -0
- package/dist/tools/autopilot-status.js +117 -0
- package/dist/tools/check-drift.d.ts +3 -0
- package/dist/tools/check-drift.js +21 -0
- package/dist/tools/create-app.d.ts +3 -0
- package/dist/tools/create-app.js +13 -0
- package/dist/tools/create-entitlement.d.ts +3 -0
- package/dist/tools/create-entitlement.js +11 -0
- package/dist/tools/create-experiment.d.ts +3 -0
- package/dist/tools/create-experiment.js +43 -0
- package/dist/tools/create-paywall.d.ts +3 -0
- package/dist/tools/create-paywall.js +18 -0
- package/dist/tools/create-pricing-strategy.d.ts +3 -0
- package/dist/tools/create-pricing-strategy.js +49 -0
- package/dist/tools/create-product.d.ts +3 -0
- package/dist/tools/create-product.js +77 -0
- package/dist/tools/create-promotion.d.ts +3 -0
- package/dist/tools/create-promotion.js +35 -0
- package/dist/tools/create-rescue-flow.d.ts +3 -0
- package/dist/tools/create-rescue-flow.js +20 -0
- package/dist/tools/delete-app.d.ts +3 -0
- package/dist/tools/delete-app.js +9 -0
- package/dist/tools/delete-entitlement.d.ts +3 -0
- package/dist/tools/delete-entitlement.js +11 -0
- package/dist/tools/delete-paywall.d.ts +3 -0
- package/dist/tools/delete-paywall.js +16 -0
- package/dist/tools/delete-product.d.ts +3 -0
- package/dist/tools/delete-product.js +9 -0
- package/dist/tools/delete-promotion.d.ts +3 -0
- package/dist/tools/delete-promotion.js +11 -0
- package/dist/tools/delete-rescue-flow.d.ts +3 -0
- package/dist/tools/delete-rescue-flow.js +11 -0
- package/dist/tools/delete-rule.d.ts +3 -0
- package/dist/tools/delete-rule.js +9 -0
- package/dist/tools/get-entitlement.d.ts +3 -0
- package/dist/tools/get-entitlement.js +9 -0
- package/dist/tools/get-experiment-summary.d.ts +3 -0
- package/dist/tools/get-experiment-summary.js +9 -0
- package/dist/tools/get-offering.d.ts +3 -0
- package/dist/tools/get-offering.js +9 -0
- package/dist/tools/get-paywall-stats.d.ts +3 -0
- package/dist/tools/get-paywall-stats.js +6 -0
- package/dist/tools/get-product.d.ts +3 -0
- package/dist/tools/get-product.js +9 -0
- package/dist/tools/get-rescue-stats.d.ts +3 -0
- package/dist/tools/get-rescue-stats.js +12 -0
- package/dist/tools/get-rule.d.ts +3 -0
- package/dist/tools/get-rule.js +9 -0
- package/dist/tools/import-products.js +7 -5
- package/dist/tools/index.js +143 -1
- package/dist/tools/list-change-requests.d.ts +3 -0
- package/dist/tools/list-change-requests.js +14 -0
- package/dist/tools/list-entitlements.d.ts +3 -0
- package/dist/tools/list-entitlements.js +6 -0
- package/dist/tools/list-paywalls.d.ts +3 -0
- package/dist/tools/list-paywalls.js +6 -0
- package/dist/tools/list-pricing-strategies.d.ts +3 -0
- package/dist/tools/list-pricing-strategies.js +8 -0
- package/dist/tools/list-promotions.d.ts +3 -0
- package/dist/tools/list-promotions.js +6 -0
- package/dist/tools/list-rescue-flows.d.ts +3 -0
- package/dist/tools/list-rescue-flows.js +6 -0
- package/dist/tools/list-rule-versions.d.ts +3 -0
- package/dist/tools/list-rule-versions.js +9 -0
- package/dist/tools/list-rules.js +4 -2
- package/dist/tools/next-step.d.ts +3 -0
- package/dist/tools/next-step.js +123 -0
- package/dist/tools/preview-pricing.d.ts +3 -0
- package/dist/tools/preview-pricing.js +13 -0
- package/dist/tools/push-prices-to-stores.d.ts +3 -0
- package/dist/tools/push-prices-to-stores.js +9 -0
- package/dist/tools/recompute-prices.d.ts +3 -0
- package/dist/tools/recompute-prices.js +24 -0
- package/dist/tools/resolve-drift.d.ts +3 -0
- package/dist/tools/resolve-drift.js +15 -0
- package/dist/tools/restore-app.d.ts +3 -0
- package/dist/tools/restore-app.js +9 -0
- package/dist/tools/revert-app-autopilot.d.ts +3 -0
- package/dist/tools/revert-app-autopilot.js +9 -0
- package/dist/tools/rollback-rule.d.ts +3 -0
- package/dist/tools/rollback-rule.js +10 -0
- package/dist/tools/run-app-autopilot.d.ts +3 -0
- package/dist/tools/run-app-autopilot.js +9 -0
- package/dist/tools/set-country-price-override.d.ts +3 -0
- package/dist/tools/set-country-price-override.js +13 -0
- package/dist/tools/setup-wizard.d.ts +3 -0
- package/dist/tools/setup-wizard.js +259 -0
- package/dist/tools/start-experiment.d.ts +3 -0
- package/dist/tools/start-experiment.js +9 -0
- package/dist/tools/status.js +25 -1
- package/dist/tools/stop-experiment.d.ts +3 -0
- package/dist/tools/stop-experiment.js +9 -0
- package/dist/tools/sync-suggestions-count.d.ts +3 -0
- package/dist/tools/sync-suggestions-count.js +6 -0
- package/dist/tools/trigger-sync.d.ts +3 -0
- package/dist/tools/trigger-sync.js +6 -0
- package/dist/tools/update-app.d.ts +3 -0
- package/dist/tools/update-app.js +19 -0
- package/dist/tools/update-entitlement.d.ts +3 -0
- package/dist/tools/update-entitlement.js +13 -0
- package/dist/tools/update-experiment.d.ts +3 -0
- package/dist/tools/update-experiment.js +22 -0
- package/dist/tools/update-offering.d.ts +3 -0
- package/dist/tools/update-offering.js +31 -0
- package/dist/tools/update-paywall.d.ts +3 -0
- package/dist/tools/update-paywall.js +23 -0
- package/dist/tools/update-product.d.ts +3 -0
- package/dist/tools/update-product.js +16 -0
- package/dist/tools/update-promotion.d.ts +3 -0
- package/dist/tools/update-promotion.js +20 -0
- package/dist/tools/update-rescue-flow.d.ts +3 -0
- package/dist/tools/update-rescue-flow.js +27 -0
- package/dist/tools/verify-setup.d.ts +3 -0
- package/dist/tools/verify-setup.js +200 -0
- package/dist/tools/whoami.d.ts +3 -0
- package/dist/tools/whoami.js +31 -0
- package/dist/types.d.ts +417 -79
- package/dist/types.js +0 -2
- package/mcp.json +89 -0
- package/package.json +8 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const QUICKSTART_GUIDE = `# Capivv Quickstart Guide
|
|
2
|
+
|
|
3
|
+
Get your app's subscriptions working in 5 steps. Follow them **in order** — each step depends on the previous one.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Step 1: Register Your App
|
|
8
|
+
|
|
9
|
+
Every app needs to be registered in Capivv with its platform and bundle ID.
|
|
10
|
+
|
|
11
|
+
**Tool:** \`capivv_create_app\`
|
|
12
|
+
**Example:**
|
|
13
|
+
- name: "My App"
|
|
14
|
+
- platform: "ios" (or "android", "web")
|
|
15
|
+
- bundle_id: "com.yourcompany.yourapp"
|
|
16
|
+
|
|
17
|
+
**Save the returned \`id\`** — you'll need it for products and offerings.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Step 2: Create Entitlements
|
|
22
|
+
|
|
23
|
+
Entitlements define **what features** users unlock when they purchase. Think of them as feature flags tied to purchases.
|
|
24
|
+
|
|
25
|
+
**Tool:** \`capivv_create_entitlement\`
|
|
26
|
+
**Example:**
|
|
27
|
+
- identifier: "pro"
|
|
28
|
+
- display_name: "Pro Access"
|
|
29
|
+
- description: "Unlocks all premium features"
|
|
30
|
+
|
|
31
|
+
Most apps need just one entitlement (e.g., "pro" or "premium"). Create more if you have tiered access.
|
|
32
|
+
|
|
33
|
+
**Save the returned \`id\`** — you'll link it to products next.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 3: Create Products
|
|
38
|
+
|
|
39
|
+
Products are the actual purchasable items (subscriptions, one-time purchases). They map 1:1 to your App Store / Google Play products.
|
|
40
|
+
|
|
41
|
+
**Tool:** \`capivv_create_product\`
|
|
42
|
+
**Example (monthly subscription):**
|
|
43
|
+
- app_id: (from Step 1)
|
|
44
|
+
- external_id: "com.yourcompany.yourapp.monthly" (must match your store product ID)
|
|
45
|
+
- product_type: "subscription"
|
|
46
|
+
- display_name: "Monthly Plan"
|
|
47
|
+
- entitlement_ids: [(entitlement ID from Step 2)]
|
|
48
|
+
- subscription: { billing_period: "month" }
|
|
49
|
+
- prices: [{ currency: "USD", amount_cents: 799, is_default: true }]
|
|
50
|
+
|
|
51
|
+
**Example (annual subscription):**
|
|
52
|
+
- app_id: (from Step 1)
|
|
53
|
+
- external_id: "com.yourcompany.yourapp.yearly"
|
|
54
|
+
- product_type: "subscription"
|
|
55
|
+
- display_name: "Annual Plan"
|
|
56
|
+
- entitlement_ids: [(entitlement ID from Step 2)]
|
|
57
|
+
- subscription: { billing_period: "year" }
|
|
58
|
+
- prices: [{ currency: "USD", amount_cents: 4999, is_default: true }]
|
|
59
|
+
|
|
60
|
+
**Important:** The \`external_id\` must exactly match the product ID in App Store Connect / Google Play Console.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Step 4: Create an Offering
|
|
65
|
+
|
|
66
|
+
An offering bundles your products into a group that your paywall displays. This is what your SDK fetches to show purchase options.
|
|
67
|
+
|
|
68
|
+
**Tool:** \`capivv_create_offering\`
|
|
69
|
+
**Example:**
|
|
70
|
+
- app_id: (from Step 1)
|
|
71
|
+
- identifier: "default" (or a custom name like "pro_plans")
|
|
72
|
+
- display_name: "Pro Plans"
|
|
73
|
+
- is_default: true
|
|
74
|
+
- packages:
|
|
75
|
+
- { identifier: "monthly", product_id: (monthly product ID), display_name: "Monthly", package_type: "monthly", position: 0 }
|
|
76
|
+
- { identifier: "annual", product_id: (annual product ID), display_name: "Annual", package_type: "annual", position: 1 }
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Step 5: Integrate the SDK
|
|
81
|
+
|
|
82
|
+
With your Capivv backend configured, integrate the SDK into your app:
|
|
83
|
+
|
|
84
|
+
### Get Your API Key
|
|
85
|
+
Go to the **Capivv dashboard → Settings → Developer → API Keys** and copy your **public key** (starts with \`capivv_pk_\`).
|
|
86
|
+
|
|
87
|
+
### iOS (Swift)
|
|
88
|
+
\`\`\`swift
|
|
89
|
+
import Capivv
|
|
90
|
+
|
|
91
|
+
// In your App init or AppDelegate
|
|
92
|
+
Capivv.configure(apiKey: "capivv_pk_...")
|
|
93
|
+
|
|
94
|
+
// Identify the user
|
|
95
|
+
Capivv.shared.login(userId: "user_123")
|
|
96
|
+
|
|
97
|
+
// Check entitlements
|
|
98
|
+
let hasAccess = try await Capivv.shared.checkEntitlement("pro")
|
|
99
|
+
|
|
100
|
+
// Fetch offerings for your paywall
|
|
101
|
+
let offerings = try await Capivv.shared.getOfferings()
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
104
|
+
### Web (JavaScript/TypeScript)
|
|
105
|
+
\`\`\`typescript
|
|
106
|
+
import { Capivv } from '@capivv/web-sdk';
|
|
107
|
+
|
|
108
|
+
const capivv = new Capivv({ apiKey: 'capivv_pk_...' });
|
|
109
|
+
await capivv.login('user_123');
|
|
110
|
+
|
|
111
|
+
const hasAccess = await capivv.checkEntitlement('pro');
|
|
112
|
+
const offerings = await capivv.getOfferings();
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Verify Your Setup
|
|
118
|
+
|
|
119
|
+
Run \`capivv_status\` to confirm everything is configured. You should see:
|
|
120
|
+
- 1+ apps
|
|
121
|
+
- 1+ entitlements
|
|
122
|
+
- 1+ products
|
|
123
|
+
- 1+ offerings
|
|
124
|
+
|
|
125
|
+
Or use \`capivv_verify_setup\` for a detailed checklist of what's ready and what's missing.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## What About Pricing in Other Countries?
|
|
130
|
+
|
|
131
|
+
You do **not** hardcode international prices. Here's how it works:
|
|
132
|
+
1. Set your **base USD price** when creating products in App Store Connect / Google Play Console
|
|
133
|
+
2. Apple and Google automatically calculate equivalent prices for other regions (you can override per-country in each console)
|
|
134
|
+
3. The Capivv SDK returns **localized price strings** from the store — your paywall shows those, not your env file values
|
|
135
|
+
|
|
136
|
+
The prices in Capivv are for **analytics and reference only**. The store is the source of truth for what users actually pay.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Common Patterns
|
|
141
|
+
|
|
142
|
+
### Trial Offer
|
|
143
|
+
Use \`capivv_apply_rule\` to create a rule that grants a free trial:
|
|
144
|
+
- condition: \`never_subscribed\`
|
|
145
|
+
- action: offer a discounted or free introductory period
|
|
146
|
+
|
|
147
|
+
### Multiple Tiers (Free / Pro / Business)
|
|
148
|
+
Create separate entitlements for each tier, then create products that grant the appropriate entitlement.
|
|
149
|
+
|
|
150
|
+
### Churn Recovery
|
|
151
|
+
Use rules with the \`churned_subscriber\` condition to show special rescue offers to users who cancelled.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Need Help?
|
|
156
|
+
|
|
157
|
+
- **Check setup status:** \`capivv_status\` or \`capivv_next_step\`
|
|
158
|
+
- **Verify everything works:** \`capivv_verify_setup\`
|
|
159
|
+
- **Browse concepts:** Read the \`capivv://docs/concepts\` resource
|
|
160
|
+
- **Platform guides:** Read \`capivv://docs/guides/ios\`, \`capivv://docs/guides/android\`, or \`capivv://docs/guides/web\`
|
|
161
|
+
`;
|
|
162
|
+
export function registerQuickstartResource(server) {
|
|
163
|
+
server.resource('quickstart', 'capivv://docs/quickstart', { mimeType: 'text/markdown' }, async (uri) => {
|
|
164
|
+
return {
|
|
165
|
+
contents: [
|
|
166
|
+
{
|
|
167
|
+
uri: uri.href,
|
|
168
|
+
text: QUICKSTART_GUIDE,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
package/dist/resources/rules.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import YAML from 'yaml';
|
|
2
3
|
export function registerRulesResource(server, client) {
|
|
3
4
|
server.resource('rule', new ResourceTemplate('capivv://rules/{ruleId}', {
|
|
4
5
|
list: async () => {
|
|
@@ -7,7 +8,7 @@ export function registerRulesResource(server, client) {
|
|
|
7
8
|
resources: rules.map((r) => ({
|
|
8
9
|
uri: `capivv://rules/${r.id}`,
|
|
9
10
|
name: r.name,
|
|
10
|
-
description: `Rule: ${r.name} (priority: ${r.priority}, ${r.
|
|
11
|
+
description: `Rule: ${r.name} (priority: ${r.priority}, ${r.status})`,
|
|
11
12
|
mimeType: 'text/yaml',
|
|
12
13
|
})),
|
|
13
14
|
};
|
|
@@ -15,11 +16,16 @@ export function registerRulesResource(server, client) {
|
|
|
15
16
|
}), { mimeType: 'text/yaml' }, async (uri, { ruleId }) => {
|
|
16
17
|
const id = Array.isArray(ruleId) ? ruleId[0] : ruleId;
|
|
17
18
|
const rule = await client.getRule(id);
|
|
19
|
+
const yamlContent = YAML.stringify({
|
|
20
|
+
conditions: rule.conditions,
|
|
21
|
+
actions: rule.actions,
|
|
22
|
+
guardrails: rule.guardrails,
|
|
23
|
+
});
|
|
18
24
|
return {
|
|
19
25
|
contents: [
|
|
20
26
|
{
|
|
21
27
|
uri: uri.href,
|
|
22
|
-
text:
|
|
28
|
+
text: yamlContent,
|
|
23
29
|
},
|
|
24
30
|
],
|
|
25
31
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerActivateRuleTool(server, client) {
|
|
3
|
+
server.tool('capivv_activate_rule', 'Activate a business rule so it starts being evaluated for users.', {
|
|
4
|
+
rule_id: z.string().describe('Rule ID to activate'),
|
|
5
|
+
}, async ({ rule_id }) => {
|
|
6
|
+
const rule = await client.activateRule(rule_id);
|
|
7
|
+
return { content: [{ type: 'text', text: JSON.stringify(rule, null, 2) }] };
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// V7 Phase C.5 — AI-callable SDK usage telemetry.
|
|
3
|
+
//
|
|
4
|
+
// Same backend endpoint the dashboard Usage dialog uses
|
|
5
|
+
// (/dashboard/settings/api-keys/:id/usage). The MCP surface exists because
|
|
6
|
+
// "is this key compromised?" is a question AI assistants are often asked —
|
|
7
|
+
// they can fetch the data, render it inline, and compare against the
|
|
8
|
+
// anomaly-detection rules documented in the runbook without the user
|
|
9
|
+
// context-switching to the dashboard.
|
|
10
|
+
export function registerApiKeyUsageTool(server, client) {
|
|
11
|
+
server.tool('capivv_api_key_usage', `Fetch request-level telemetry for a specific publishable API key (pk_live_* / pk_test_*). Returns hourly buckets of request counts, error counts, distinct IPs, SDK versions, and country codes — the same data the dashboard's Usage tab renders. Use this to investigate a suspected key leak: look for sudden IP spread, unexpected country codes, or rate spikes vs. a prior baseline.`, {
|
|
12
|
+
api_key_id: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('The api_keys.id of the key to inspect (UUID). Use capivv_whoami to see available apps — key IDs appear under each app in the dashboard Settings → Developer page.'),
|
|
15
|
+
hours: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(168)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Window in hours to summarize over (default 24, max 168 = 7 days).'),
|
|
22
|
+
}, async ({ api_key_id, hours }) => {
|
|
23
|
+
const usage = await client.getApiKeyUsage(api_key_id, hours ?? 24);
|
|
24
|
+
const lines = [];
|
|
25
|
+
lines.push(`Usage for key ${api_key_id.slice(0, 8)}… over the last ${usage.hours} hour(s):`);
|
|
26
|
+
lines.push('');
|
|
27
|
+
lines.push(` Requests: ${usage.summary.total_requests.toLocaleString()}`);
|
|
28
|
+
lines.push(` Errors: ${usage.summary.total_errors.toLocaleString()}`);
|
|
29
|
+
lines.push(` Unique IPs: ${usage.summary.distinct_ips}`);
|
|
30
|
+
lines.push(` Countries: ${usage.summary.distinct_countries}`);
|
|
31
|
+
lines.push(` SDK versions: ${usage.summary.distinct_sdk_versions}`);
|
|
32
|
+
lines.push('');
|
|
33
|
+
// Anomaly signals inline — surface the same ones the hourly scanner
|
|
34
|
+
// checks in capivv-workers::api_key_anomaly. AI assistant can quote
|
|
35
|
+
// these directly when answering "is this key compromised?"
|
|
36
|
+
const signals = [];
|
|
37
|
+
if (usage.summary.distinct_ips >= 5) {
|
|
38
|
+
signals.push(` • ${usage.summary.distinct_ips} distinct IPs is high for a single app — investigate if this is expected.`);
|
|
39
|
+
}
|
|
40
|
+
if (usage.summary.distinct_countries >= 3) {
|
|
41
|
+
signals.push(` • Traffic from ${usage.summary.distinct_countries} countries. If your app isn't multi-region, consider revoking.`);
|
|
42
|
+
}
|
|
43
|
+
if (usage.summary.total_requests > 10_000 && usage.hours <= 24) {
|
|
44
|
+
signals.push(` • ${usage.summary.total_requests.toLocaleString()} requests in ${usage.hours}h crosses the 10k/hour absolute-volume threshold.`);
|
|
45
|
+
}
|
|
46
|
+
if (signals.length > 0) {
|
|
47
|
+
lines.push('Anomaly signals:');
|
|
48
|
+
lines.push(...signals);
|
|
49
|
+
lines.push('');
|
|
50
|
+
}
|
|
51
|
+
if (usage.samples.length === 0) {
|
|
52
|
+
lines.push('No traffic recorded in the selected window.');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push(`Sample buckets (showing up to 10 of ${usage.samples.length}):`);
|
|
56
|
+
for (const s of usage.samples.slice(0, 10)) {
|
|
57
|
+
const ip = s.client_ip ?? 'unknown';
|
|
58
|
+
const sdk = s.sdk_version ?? 'unknown';
|
|
59
|
+
const country = s.country_code ?? '??';
|
|
60
|
+
lines.push(` ${s.bucket_start} ${s.request_count}req/${s.error_count}err ${ip} ${sdk} ${country}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{ type: 'text', text: lines.join('\n') },
|
|
66
|
+
{ type: 'text', text: JSON.stringify(usage, null, 2) },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/tools/apply-rule.js
CHANGED
|
@@ -1,33 +1,74 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import YAML from 'yaml';
|
|
2
3
|
export function registerApplyRuleTool(server, client) {
|
|
3
|
-
server.tool('capivv_apply_rule',
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
server.tool('capivv_apply_rule', `Apply a YAML rule configuration. Validates the rule first, then creates or updates it.
|
|
5
|
+
|
|
6
|
+
Example YAML:
|
|
7
|
+
conditions:
|
|
8
|
+
- type: never_subscribed
|
|
9
|
+
actions:
|
|
10
|
+
- type: grant_entitlement
|
|
11
|
+
identifier: "trial_access"
|
|
12
|
+
expires_in_days: 7
|
|
13
|
+
reason: "New user trial"
|
|
14
|
+
|
|
15
|
+
Condition types: audience, attribute, date_range, percentage, has_entitlement, has_product, subscription_status, renewal_window, is_trial_period, never_subscribed, churned_subscriber, country, platform, app_version
|
|
16
|
+
Action types: override_price, discount_percent, discount_fixed, grant_entitlement, set_paywall, show_offering, cancel_offer, track_event, set_attribute`, {
|
|
17
|
+
name: z.string().describe('Rule name (used as both identifier and display name)'),
|
|
18
|
+
content: z.string().describe('YAML rule content with conditions and actions arrays'),
|
|
6
19
|
priority: z.number().optional().default(100).describe('Rule priority (lower = higher priority)'),
|
|
7
20
|
is_enabled: z.boolean().optional().default(true).describe('Whether the rule is active'),
|
|
8
21
|
}, async ({ name, content, priority, is_enabled }) => {
|
|
22
|
+
// Parse YAML to extract conditions, actions, guardrails
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = YAML.parse(content);
|
|
26
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
27
|
+
throw new Error('YAML must be an object with conditions and/or actions');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
33
|
+
success: false,
|
|
34
|
+
error: `Invalid YAML: ${e instanceof Error ? e.message : String(e)}`,
|
|
35
|
+
}, null, 2) }],
|
|
36
|
+
isError: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const conditions = parsed.conditions ?? [];
|
|
40
|
+
const actions = parsed.actions ?? [];
|
|
41
|
+
const guardrails = parsed.guardrails;
|
|
9
42
|
// Validate first
|
|
10
|
-
const validation = await client.validateRule(
|
|
43
|
+
const validation = await client.validateRule({ conditions, actions, guardrails });
|
|
11
44
|
if (!validation.is_valid) {
|
|
12
|
-
const result = {
|
|
13
|
-
success: false,
|
|
14
|
-
validation_errors: validation.errors,
|
|
15
|
-
validation_warnings: validation.warnings,
|
|
16
|
-
};
|
|
17
45
|
return {
|
|
18
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
46
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
47
|
+
success: false,
|
|
48
|
+
validation_errors: validation.errors,
|
|
49
|
+
validation_warnings: validation.warnings,
|
|
50
|
+
}, null, 2) }],
|
|
19
51
|
isError: true,
|
|
20
52
|
};
|
|
21
53
|
}
|
|
22
|
-
// Create
|
|
23
|
-
const
|
|
54
|
+
// Create the rule
|
|
55
|
+
const identifier = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
56
|
+
const rule = await client.createRule({
|
|
57
|
+
identifier,
|
|
58
|
+
name,
|
|
59
|
+
conditions,
|
|
60
|
+
actions,
|
|
61
|
+
guardrails,
|
|
62
|
+
priority,
|
|
63
|
+
});
|
|
24
64
|
const result = {
|
|
25
65
|
success: true,
|
|
26
66
|
rule: {
|
|
27
67
|
id: rule.id,
|
|
68
|
+
identifier: rule.identifier,
|
|
28
69
|
name: rule.name,
|
|
29
70
|
priority: rule.priority,
|
|
30
|
-
|
|
71
|
+
status: rule.status,
|
|
31
72
|
updated_at: rule.updated_at,
|
|
32
73
|
},
|
|
33
74
|
validation_warnings: validation.warnings.length > 0 ? validation.warnings : undefined,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerApproveChangeRequestTool(server, client) {
|
|
3
|
+
server.tool('capivv_approve_change_request', 'Approve a pending pricing change request. The new price is applied to Capivv immediately. Use capivv_push_prices_to_stores afterwards to propagate to App Store Connect / Google Play.', {
|
|
4
|
+
change_request_id: z.string().describe('Change request ID to approve'),
|
|
5
|
+
}, async ({ change_request_id }) => {
|
|
6
|
+
await client.approvePricingChangeRequest(change_request_id);
|
|
7
|
+
return {
|
|
8
|
+
content: [
|
|
9
|
+
{
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: `Change request ${change_request_id} approved. The new price is now in Capivv. Call capivv_push_prices_to_stores to propagate to the stores.`,
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerArchiveAppTool(server, client) {
|
|
3
|
+
server.tool('capivv_archive_app', 'Archive an app (soft delete). The app stops serving new traffic but its data is preserved. Use capivv_restore_app to bring it back. Prefer this over capivv_delete_app unless permanent removal is required.', {
|
|
4
|
+
app_id: z.string().describe('App ID to archive'),
|
|
5
|
+
}, async ({ app_id }) => {
|
|
6
|
+
const app = await client.archiveApp(app_id);
|
|
7
|
+
return { content: [{ type: 'text', text: JSON.stringify(app, null, 2) }] };
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// V7 B.5 — capivv_autopilot_status
|
|
3
|
+
//
|
|
4
|
+
// Answers "show me everything autopilot did for this app" from one MCP call.
|
|
5
|
+
// Composed from the existing listOfferings / listRules / listExperiments
|
|
6
|
+
// client methods plus the listApps lookup to resolve the app name — no new
|
|
7
|
+
// backend endpoint. Paywalls + rescue flows + promotions live in the same
|
|
8
|
+
// autopilot provenance story but don't have MCP list methods today; the tool
|
|
9
|
+
// surfaces a link to the dashboard pages where those badges also render.
|
|
10
|
+
export function registerAutopilotStatusTool(server, client) {
|
|
11
|
+
server.tool('capivv_autopilot_status', `Show everything autopilot generated for a specific app — offerings, entitlement rules, and experiments, filtered by the generated_by provenance marker. Useful right after capivv_setup_wizard or import-apps to confirm what was auto-created, or when a user asks "what did autopilot do for app X?"`, {
|
|
12
|
+
app_id: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('The Capivv app ID to inspect. Use capivv_list_apps to find it, or capivv_whoami to see all apps reachable from your API key.'),
|
|
15
|
+
}, async ({ app_id }) => {
|
|
16
|
+
const [apps, offerings, rules, experiments] = await Promise.all([
|
|
17
|
+
client.listApps(),
|
|
18
|
+
client.listOfferings(),
|
|
19
|
+
client.listRules(),
|
|
20
|
+
client.listExperiments(),
|
|
21
|
+
]);
|
|
22
|
+
const app = apps.find((a) => a.id === app_id);
|
|
23
|
+
if (!app) {
|
|
24
|
+
return {
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: `No app found with id ${app_id}. Available apps: ${apps.map((a) => `${a.name} (${a.id})`).join(', ') || 'none'}`,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// generated_by uses an `autopilot_v` prefix across versions. Matching
|
|
35
|
+
// on the prefix lets the tool show v1 + v2 resources without hard-
|
|
36
|
+
// coding version strings that'll drift.
|
|
37
|
+
const autopilotStamp = (gb) => typeof gb === 'string' && gb.startsWith('autopilot_');
|
|
38
|
+
const autopilotOfferings = offerings.filter((o) => o.app_id === app_id && autopilotStamp(o.generated_by));
|
|
39
|
+
const autopilotRules = rules.filter((r) => {
|
|
40
|
+
if (!autopilotStamp(r.generated_by))
|
|
41
|
+
return false;
|
|
42
|
+
// Rules are tenant-scoped; we tagged the source app_id in metadata.
|
|
43
|
+
const md = r.metadata;
|
|
44
|
+
return md?.generated_for_app_id === app_id;
|
|
45
|
+
});
|
|
46
|
+
const autopilotExperiments = experiments.filter((e) => {
|
|
47
|
+
if (!autopilotStamp(e.generated_by))
|
|
48
|
+
return false;
|
|
49
|
+
const md = e.metadata;
|
|
50
|
+
return md?.generated_for_app_id === app_id;
|
|
51
|
+
});
|
|
52
|
+
const totals = {
|
|
53
|
+
offerings: autopilotOfferings.length,
|
|
54
|
+
rules: autopilotRules.length,
|
|
55
|
+
experiments: autopilotExperiments.length,
|
|
56
|
+
};
|
|
57
|
+
const nothing = totals.offerings + totals.rules + totals.experiments === 0;
|
|
58
|
+
const lines = [];
|
|
59
|
+
lines.push(`Autopilot status for ${app.name} (${app.bundle_id ?? app_id})`);
|
|
60
|
+
lines.push('');
|
|
61
|
+
if (nothing) {
|
|
62
|
+
lines.push("No autopilot-generated resources for this app (yet). If you just ran capivv_setup_wizard or imported products, try again — autopilot runs inline during import.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
lines.push(`Summary: ${totals.offerings} offering, ${totals.rules} rule(s), ${totals.experiments} experiment(s).`);
|
|
66
|
+
lines.push('');
|
|
67
|
+
if (autopilotOfferings.length > 0) {
|
|
68
|
+
lines.push('Offerings:');
|
|
69
|
+
for (const o of autopilotOfferings) {
|
|
70
|
+
lines.push(` • ${o.identifier} — ${o.display_name} [${o.generated_by}]`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (autopilotRules.length > 0) {
|
|
74
|
+
lines.push('Rules:');
|
|
75
|
+
for (const r of autopilotRules) {
|
|
76
|
+
lines.push(` • ${r.identifier} — ${r.name} [${r.generated_by}]`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (autopilotExperiments.length > 0) {
|
|
80
|
+
lines.push('Experiments:');
|
|
81
|
+
for (const e of autopilotExperiments) {
|
|
82
|
+
lines.push(` • ${e.name} — status: ${e.status} [${e.generated_by}]`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push('Paywalls + rescue flows + promotions also carry the "Autopilot" badge — open /paywalls, /rescue, /promotions in the dashboard to see them there.');
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{ type: 'text', text: lines.join('\n') },
|
|
91
|
+
{
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: JSON.stringify({
|
|
94
|
+
app: { id: app_id, name: app.name, bundle_id: app.bundle_id },
|
|
95
|
+
totals,
|
|
96
|
+
offerings: autopilotOfferings.map((o) => ({
|
|
97
|
+
id: o.id,
|
|
98
|
+
identifier: o.identifier,
|
|
99
|
+
generated_by: o.generated_by,
|
|
100
|
+
})),
|
|
101
|
+
rules: autopilotRules.map((r) => ({
|
|
102
|
+
id: r.id,
|
|
103
|
+
identifier: r.identifier,
|
|
104
|
+
generated_by: r.generated_by,
|
|
105
|
+
})),
|
|
106
|
+
experiments: autopilotExperiments.map((e) => ({
|
|
107
|
+
id: e.id,
|
|
108
|
+
name: e.name,
|
|
109
|
+
status: e.status,
|
|
110
|
+
generated_by: e.generated_by,
|
|
111
|
+
})),
|
|
112
|
+
}, null, 2),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerCheckDriftTool(server, client) {
|
|
3
|
+
server.tool('capivv_check_drift', [
|
|
4
|
+
'List drift between Capivv and the connected store(s) — products that exist in the store but not in Capivv, prices that differ, metadata that changed, products deleted from the store.',
|
|
5
|
+
'Each entry has a `suggestion_type` (new_product | price_changed | metadata_changed | product_deleted) and a `diff_details` payload describing the mismatch.',
|
|
6
|
+
'Use capivv_resolve_drift to accept (apply the change) or dismiss (ignore) each one.',
|
|
7
|
+
'Drift is detected continuously by the background sync worker; call capivv_trigger_sync to force an immediate scan if you suspect the listing is stale.',
|
|
8
|
+
].join(' '), {
|
|
9
|
+
app_id: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Filter by app. When omitted, the API returns drift for the first app in the workspace.'),
|
|
13
|
+
status: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Filter by status: pending (default), accepted, dismissed, auto_resolved'),
|
|
17
|
+
}, async ({ app_id, status }) => {
|
|
18
|
+
const suggestions = await client.listSyncSuggestions(app_id, status);
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(suggestions, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerCreateAppTool(server, client) {
|
|
3
|
+
server.tool('capivv_create_app', 'Create a new app in Capivv with an explicit bundle ID. Prefer the dashboard\'s store-connect flow (which imports apps with real bundle IDs automatically); this tool is the manual fallback for web apps or apps without a store presence.', {
|
|
4
|
+
name: z.string().describe('App name (e.g., "My App")'),
|
|
5
|
+
platform: z.string().describe('Platform: ios, android, or web'),
|
|
6
|
+
bundle_id: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe('Bundle identifier. Use your real reverse-DNS ID (e.g., "com.yourcompany.yourapp") — do NOT use com.example.myapp or other placeholder values.'),
|
|
9
|
+
}, async (args) => {
|
|
10
|
+
const app = await client.createApp(args);
|
|
11
|
+
return { content: [{ type: 'text', text: JSON.stringify(app, null, 2) }] };
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerCreateEntitlementTool(server, client) {
|
|
3
|
+
server.tool('capivv_create_entitlement', 'Create a new entitlement (feature access identifier). Products grant entitlements to users upon purchase.', {
|
|
4
|
+
identifier: z.string().describe('Unique identifier (e.g., "premium", "pro_features")'),
|
|
5
|
+
display_name: z.string().describe('Human-readable name (e.g., "Premium Access")'),
|
|
6
|
+
description: z.string().optional().describe('Optional description of what this entitlement unlocks'),
|
|
7
|
+
}, async (args) => {
|
|
8
|
+
const entitlement = await client.createEntitlement(args);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(entitlement, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
}
|