@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.
Files changed (141) hide show
  1. package/README.md +73 -2
  2. package/dist/client.d.ts +62 -3
  3. package/dist/client.js +220 -5
  4. package/dist/config.js +1 -1
  5. package/dist/http.d.ts +12 -0
  6. package/dist/http.js +102 -0
  7. package/dist/resources/guides.d.ts +2 -0
  8. package/dist/resources/guides.js +81 -0
  9. package/dist/resources/index.js +4 -0
  10. package/dist/resources/quickstart.d.ts +2 -0
  11. package/dist/resources/quickstart.js +173 -0
  12. package/dist/resources/rules.js +8 -2
  13. package/dist/tools/activate-rule.d.ts +3 -0
  14. package/dist/tools/activate-rule.js +9 -0
  15. package/dist/tools/api-key-usage.d.ts +3 -0
  16. package/dist/tools/api-key-usage.js +70 -0
  17. package/dist/tools/apply-rule.js +54 -13
  18. package/dist/tools/approve-change-request.d.ts +3 -0
  19. package/dist/tools/approve-change-request.js +16 -0
  20. package/dist/tools/archive-app.d.ts +3 -0
  21. package/dist/tools/archive-app.js +9 -0
  22. package/dist/tools/autopilot-status.d.ts +3 -0
  23. package/dist/tools/autopilot-status.js +117 -0
  24. package/dist/tools/check-drift.d.ts +3 -0
  25. package/dist/tools/check-drift.js +21 -0
  26. package/dist/tools/create-app.d.ts +3 -0
  27. package/dist/tools/create-app.js +13 -0
  28. package/dist/tools/create-entitlement.d.ts +3 -0
  29. package/dist/tools/create-entitlement.js +11 -0
  30. package/dist/tools/create-experiment.d.ts +3 -0
  31. package/dist/tools/create-experiment.js +43 -0
  32. package/dist/tools/create-paywall.d.ts +3 -0
  33. package/dist/tools/create-paywall.js +18 -0
  34. package/dist/tools/create-pricing-strategy.d.ts +3 -0
  35. package/dist/tools/create-pricing-strategy.js +49 -0
  36. package/dist/tools/create-product.d.ts +3 -0
  37. package/dist/tools/create-product.js +77 -0
  38. package/dist/tools/create-promotion.d.ts +3 -0
  39. package/dist/tools/create-promotion.js +35 -0
  40. package/dist/tools/create-rescue-flow.d.ts +3 -0
  41. package/dist/tools/create-rescue-flow.js +20 -0
  42. package/dist/tools/delete-app.d.ts +3 -0
  43. package/dist/tools/delete-app.js +9 -0
  44. package/dist/tools/delete-entitlement.d.ts +3 -0
  45. package/dist/tools/delete-entitlement.js +11 -0
  46. package/dist/tools/delete-paywall.d.ts +3 -0
  47. package/dist/tools/delete-paywall.js +16 -0
  48. package/dist/tools/delete-product.d.ts +3 -0
  49. package/dist/tools/delete-product.js +9 -0
  50. package/dist/tools/delete-promotion.d.ts +3 -0
  51. package/dist/tools/delete-promotion.js +11 -0
  52. package/dist/tools/delete-rescue-flow.d.ts +3 -0
  53. package/dist/tools/delete-rescue-flow.js +11 -0
  54. package/dist/tools/delete-rule.d.ts +3 -0
  55. package/dist/tools/delete-rule.js +9 -0
  56. package/dist/tools/get-entitlement.d.ts +3 -0
  57. package/dist/tools/get-entitlement.js +9 -0
  58. package/dist/tools/get-experiment-summary.d.ts +3 -0
  59. package/dist/tools/get-experiment-summary.js +9 -0
  60. package/dist/tools/get-offering.d.ts +3 -0
  61. package/dist/tools/get-offering.js +9 -0
  62. package/dist/tools/get-paywall-stats.d.ts +3 -0
  63. package/dist/tools/get-paywall-stats.js +6 -0
  64. package/dist/tools/get-product.d.ts +3 -0
  65. package/dist/tools/get-product.js +9 -0
  66. package/dist/tools/get-rescue-stats.d.ts +3 -0
  67. package/dist/tools/get-rescue-stats.js +12 -0
  68. package/dist/tools/get-rule.d.ts +3 -0
  69. package/dist/tools/get-rule.js +9 -0
  70. package/dist/tools/import-products.js +7 -5
  71. package/dist/tools/index.js +143 -1
  72. package/dist/tools/list-change-requests.d.ts +3 -0
  73. package/dist/tools/list-change-requests.js +14 -0
  74. package/dist/tools/list-entitlements.d.ts +3 -0
  75. package/dist/tools/list-entitlements.js +6 -0
  76. package/dist/tools/list-paywalls.d.ts +3 -0
  77. package/dist/tools/list-paywalls.js +6 -0
  78. package/dist/tools/list-pricing-strategies.d.ts +3 -0
  79. package/dist/tools/list-pricing-strategies.js +8 -0
  80. package/dist/tools/list-promotions.d.ts +3 -0
  81. package/dist/tools/list-promotions.js +6 -0
  82. package/dist/tools/list-rescue-flows.d.ts +3 -0
  83. package/dist/tools/list-rescue-flows.js +6 -0
  84. package/dist/tools/list-rule-versions.d.ts +3 -0
  85. package/dist/tools/list-rule-versions.js +9 -0
  86. package/dist/tools/list-rules.js +4 -2
  87. package/dist/tools/next-step.d.ts +3 -0
  88. package/dist/tools/next-step.js +123 -0
  89. package/dist/tools/preview-pricing.d.ts +3 -0
  90. package/dist/tools/preview-pricing.js +13 -0
  91. package/dist/tools/push-prices-to-stores.d.ts +3 -0
  92. package/dist/tools/push-prices-to-stores.js +9 -0
  93. package/dist/tools/recompute-prices.d.ts +3 -0
  94. package/dist/tools/recompute-prices.js +24 -0
  95. package/dist/tools/resolve-drift.d.ts +3 -0
  96. package/dist/tools/resolve-drift.js +15 -0
  97. package/dist/tools/restore-app.d.ts +3 -0
  98. package/dist/tools/restore-app.js +9 -0
  99. package/dist/tools/revert-app-autopilot.d.ts +3 -0
  100. package/dist/tools/revert-app-autopilot.js +9 -0
  101. package/dist/tools/rollback-rule.d.ts +3 -0
  102. package/dist/tools/rollback-rule.js +10 -0
  103. package/dist/tools/run-app-autopilot.d.ts +3 -0
  104. package/dist/tools/run-app-autopilot.js +9 -0
  105. package/dist/tools/set-country-price-override.d.ts +3 -0
  106. package/dist/tools/set-country-price-override.js +13 -0
  107. package/dist/tools/setup-wizard.d.ts +3 -0
  108. package/dist/tools/setup-wizard.js +259 -0
  109. package/dist/tools/start-experiment.d.ts +3 -0
  110. package/dist/tools/start-experiment.js +9 -0
  111. package/dist/tools/status.js +25 -1
  112. package/dist/tools/stop-experiment.d.ts +3 -0
  113. package/dist/tools/stop-experiment.js +9 -0
  114. package/dist/tools/sync-suggestions-count.d.ts +3 -0
  115. package/dist/tools/sync-suggestions-count.js +6 -0
  116. package/dist/tools/trigger-sync.d.ts +3 -0
  117. package/dist/tools/trigger-sync.js +6 -0
  118. package/dist/tools/update-app.d.ts +3 -0
  119. package/dist/tools/update-app.js +19 -0
  120. package/dist/tools/update-entitlement.d.ts +3 -0
  121. package/dist/tools/update-entitlement.js +13 -0
  122. package/dist/tools/update-experiment.d.ts +3 -0
  123. package/dist/tools/update-experiment.js +22 -0
  124. package/dist/tools/update-offering.d.ts +3 -0
  125. package/dist/tools/update-offering.js +31 -0
  126. package/dist/tools/update-paywall.d.ts +3 -0
  127. package/dist/tools/update-paywall.js +23 -0
  128. package/dist/tools/update-product.d.ts +3 -0
  129. package/dist/tools/update-product.js +16 -0
  130. package/dist/tools/update-promotion.d.ts +3 -0
  131. package/dist/tools/update-promotion.js +20 -0
  132. package/dist/tools/update-rescue-flow.d.ts +3 -0
  133. package/dist/tools/update-rescue-flow.js +27 -0
  134. package/dist/tools/verify-setup.d.ts +3 -0
  135. package/dist/tools/verify-setup.js +200 -0
  136. package/dist/tools/whoami.d.ts +3 -0
  137. package/dist/tools/whoami.js +31 -0
  138. package/dist/types.d.ts +417 -79
  139. package/dist/types.js +0 -2
  140. package/mcp.json +89 -0
  141. package/package.json +8 -2
@@ -0,0 +1,259 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Only the messages we're confident indicate a conflict (existing row) should
4
+ * trigger the "list and reuse" fallback. Anything else — timeouts, 5xx,
5
+ * validation errors — is a legitimate failure that the caller needs to see.
6
+ */
7
+ function isConflictError(msg) {
8
+ const lower = msg.toLowerCase();
9
+ return (lower.includes('already exists') ||
10
+ lower.includes('duplicate') ||
11
+ lower.includes('409') ||
12
+ lower.includes('conflict'));
13
+ }
14
+ export function registerSetupWizardTool(server, client) {
15
+ server.tool('capivv_setup_wizard', `One-shot setup for an EXISTING app: creates an entitlement, products, and offering on an app you've already imported via the Capivv dashboard's store-connect flow.
16
+
17
+ As of V6 this tool does NOT create apps from scratch — connect your store in the dashboard to import apps with real bundle IDs. Use capivv_list_apps or capivv_whoami to see which apps are available.
18
+
19
+ Example:
20
+ bundle_id: "com.mycompany.myapp" // must already exist; omit if your tenant has exactly one app
21
+ entitlement_id: "pro"
22
+ products: [
23
+ { name: "Monthly", external_id: "com.mycompany.myapp.monthly", billing_period: "month", price_cents: 799 },
24
+ { name: "Annual", external_id: "com.mycompany.myapp.yearly", billing_period: "year", price_cents: 4999 }
25
+ ]
26
+ offering_id: "default"`, {
27
+ bundle_id: z
28
+ .string()
29
+ .optional()
30
+ .describe('Bundle ID of the existing app to configure. If omitted and the tenant has exactly one app, that app is used.'),
31
+ entitlement_id: z.string().describe('Entitlement identifier (e.g., "pro", "premium")'),
32
+ entitlement_name: z
33
+ .string()
34
+ .optional()
35
+ .describe('Entitlement display name (defaults to capitalized identifier)'),
36
+ products: z
37
+ .array(z.object({
38
+ name: z.string().describe('Product display name (e.g., "Monthly Plan")'),
39
+ external_id: z
40
+ .string()
41
+ .describe('Store product ID. With a connected store integration, Capivv creates this product in App Store Connect / Google Play too — pass the ID you want it to have there.'),
42
+ billing_period: z
43
+ .string()
44
+ .describe('Billing period: week, month, three_months, six_months, or year'),
45
+ price_cents: z.number().describe('Price in USD cents (e.g., 799 for $7.99)'),
46
+ }))
47
+ .describe('Subscription products to create'),
48
+ offering_id: z.string().optional().describe('Offering identifier (default: "default")'),
49
+ offering_name: z
50
+ .string()
51
+ .optional()
52
+ .describe('Offering display name (default: based on entitlement)'),
53
+ }, async (args) => {
54
+ // Step 0: resolve identity + reachable apps. Every wizard response
55
+ // carries `tenant` so the caller can see which workspace is being
56
+ // written to — if this org_name doesn't match expectations, stop.
57
+ const me = await client.whoami();
58
+ const result = {
59
+ success: true,
60
+ tenant: { tenant_id: me.tenant_id, org_name: me.org_name },
61
+ resolved_app: null,
62
+ created: { entitlements: [], products: [] },
63
+ skipped_existing: [],
64
+ errors: [],
65
+ next_steps: [],
66
+ };
67
+ // Step 1: resolve which app to configure. No more hardcoded placeholder
68
+ // app creation — the app must already exist via dashboard import.
69
+ let selected = null;
70
+ if (me.apps.length === 0) {
71
+ result.success = false;
72
+ result.errors.push(`Workspace "${me.org_name}" has no apps yet. Connect your store in the dashboard (Settings → Developer → Integrations) and import at least one app first, then re-run this wizard.`);
73
+ return {
74
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
75
+ };
76
+ }
77
+ if (args.bundle_id) {
78
+ const match = me.apps.find((a) => a.bundle_id === args.bundle_id);
79
+ if (!match) {
80
+ result.success = false;
81
+ result.errors.push(`No app with bundle_id "${args.bundle_id}" in workspace "${me.org_name}". Available: ${me.apps.map((a) => a.bundle_id).join(', ')}.`);
82
+ return {
83
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
84
+ };
85
+ }
86
+ // whoami returns a subset of App fields — fetch the full row so we
87
+ // can use it as the appId source.
88
+ const apps = await client.listApps();
89
+ selected = apps.find((a) => a.id === match.id) ?? null;
90
+ }
91
+ else if (me.apps.length === 1) {
92
+ const apps = await client.listApps();
93
+ selected = apps.find((a) => a.id === me.apps[0].id) ?? null;
94
+ }
95
+ else {
96
+ result.success = false;
97
+ result.errors.push(`Workspace "${me.org_name}" has ${me.apps.length} apps — pass bundle_id to pick one. Options: ${me.apps.map((a) => `${a.bundle_id} (${a.name})`).join(', ')}.`);
98
+ return {
99
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
100
+ };
101
+ }
102
+ if (!selected) {
103
+ result.success = false;
104
+ result.errors.push('Failed to resolve app from the tenant');
105
+ return {
106
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
107
+ };
108
+ }
109
+ result.resolved_app = {
110
+ id: selected.id,
111
+ name: selected.name,
112
+ bundle_id: selected.bundle_id ?? '',
113
+ platform: selected.platform,
114
+ };
115
+ const appId = selected.id;
116
+ // Step 2: Create entitlement. Conflict (already exists) is treated as a
117
+ // skip, not a failure; everything else aborts loud.
118
+ let entitlementId;
119
+ try {
120
+ const entitlement = await client.createEntitlement({
121
+ identifier: args.entitlement_id,
122
+ display_name: args.entitlement_name ??
123
+ args.entitlement_id.charAt(0).toUpperCase() + args.entitlement_id.slice(1),
124
+ });
125
+ result.created.entitlements.push(entitlement);
126
+ entitlementId = entitlement.id;
127
+ }
128
+ catch (err) {
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ if (isConflictError(msg)) {
131
+ const entitlements = await client.listEntitlements();
132
+ const existing = entitlements.find((e) => e.identifier === args.entitlement_id);
133
+ if (existing) {
134
+ entitlementId = existing.id;
135
+ result.skipped_existing.push(`entitlement:${args.entitlement_id}`);
136
+ }
137
+ else {
138
+ result.success = false;
139
+ result.errors.push(`Entitlement "${args.entitlement_id}" reported as existing but couldn't be found — aborting`);
140
+ return {
141
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
142
+ };
143
+ }
144
+ }
145
+ else {
146
+ result.success = false;
147
+ result.errors.push(`Entitlement creation failed: ${msg}`);
148
+ return {
149
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
150
+ };
151
+ }
152
+ }
153
+ // Step 3: Create products
154
+ const PERIOD_TO_PACKAGE = {
155
+ week: 'weekly',
156
+ month: 'monthly',
157
+ three_months: 'three_month',
158
+ six_months: 'six_month',
159
+ year: 'annual',
160
+ };
161
+ for (const p of args.products) {
162
+ try {
163
+ const product = await client.createProduct({
164
+ app_id: appId,
165
+ external_id: p.external_id,
166
+ product_type: 'subscription',
167
+ display_name: p.name,
168
+ entitlement_ids: entitlementId ? [entitlementId] : [],
169
+ subscription: { billing_period: p.billing_period },
170
+ prices: [{ currency: 'USD', amount_cents: p.price_cents, is_default: true }],
171
+ });
172
+ result.created.products.push(product);
173
+ }
174
+ catch (err) {
175
+ const msg = err instanceof Error ? err.message : String(err);
176
+ if (isConflictError(msg)) {
177
+ const products = await client.listProducts(appId);
178
+ const existing = products.find((pr) => pr.external_id === p.external_id);
179
+ if (existing) {
180
+ result.created.products.push(existing);
181
+ result.skipped_existing.push(`product:${p.external_id}`);
182
+ }
183
+ else {
184
+ result.success = false;
185
+ result.errors.push(`Product "${p.name}" reported as existing but couldn't be found — aborting`);
186
+ return {
187
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
188
+ };
189
+ }
190
+ }
191
+ else {
192
+ result.success = false;
193
+ result.errors.push(`Product "${p.name}" creation failed: ${msg}`);
194
+ return {
195
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
196
+ };
197
+ }
198
+ }
199
+ }
200
+ // Step 4: Create offering
201
+ if (result.created.products.length > 0) {
202
+ const offeringId = args.offering_id ?? 'default';
203
+ const offeringName = args.offering_name ??
204
+ `${args.entitlement_id.charAt(0).toUpperCase() + args.entitlement_id.slice(1)} Plans`;
205
+ const packages = result.created.products.map((product, index) => {
206
+ const inputProduct = args.products.find((p) => p.external_id === product.external_id);
207
+ const packageType = inputProduct
208
+ ? (PERIOD_TO_PACKAGE[inputProduct.billing_period] ?? 'custom')
209
+ : 'custom';
210
+ return {
211
+ identifier: packageType,
212
+ product_id: product.id,
213
+ display_name: product.display_name,
214
+ package_type: packageType,
215
+ position: index,
216
+ };
217
+ });
218
+ try {
219
+ result.created.offering = await client.createOffering({
220
+ app_id: appId,
221
+ identifier: offeringId,
222
+ display_name: offeringName,
223
+ is_default: true,
224
+ packages,
225
+ });
226
+ }
227
+ catch (err) {
228
+ const msg = err instanceof Error ? err.message : String(err);
229
+ if (isConflictError(msg)) {
230
+ const offerings = await client.listOfferings();
231
+ const existing = offerings.find((o) => o.identifier === offeringId);
232
+ if (existing) {
233
+ result.created.offering = existing;
234
+ result.skipped_existing.push(`offering:${offeringId}`);
235
+ }
236
+ else {
237
+ result.success = false;
238
+ result.errors.push(`Offering "${offeringId}" reported as existing but couldn't be found — aborting`);
239
+ return {
240
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
241
+ };
242
+ }
243
+ }
244
+ else {
245
+ result.success = false;
246
+ result.errors.push(`Offering creation failed: ${msg}`);
247
+ return {
248
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
249
+ };
250
+ }
251
+ }
252
+ }
253
+ // Build next steps
254
+ result.next_steps.push(`Wrote to workspace "${result.tenant.org_name}" (tenant_id: ${result.tenant.tenant_id}, app: ${result.resolved_app.bundle_id}). If that's the wrong workspace, switch your CAPIVV_API_KEY env var before continuing.`);
255
+ result.next_steps.push('Integrate the SDK: copy the publishable (pk_*) key generated for this app from Settings → Developer → API Keys. Read capivv://docs/quickstart for code examples.');
256
+ result.next_steps.push('Verify: Run capivv_verify_setup to check that everything is configured correctly.');
257
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
258
+ });
259
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerStartExperimentTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export function registerStartExperimentTool(server, client) {
3
+ server.tool('capivv_start_experiment', 'Flip an experiment from draft to running. New users now get assigned to variants based on traffic_percent. Use capivv_get_experiment_summary to monitor progress.', {
4
+ experiment_id: z.string().describe('Experiment ID to start'),
5
+ }, async ({ experiment_id }) => {
6
+ const exp = await client.startExperiment(experiment_id);
7
+ return { content: [{ type: 'text', text: JSON.stringify(exp, null, 2) }] };
8
+ });
9
+ }
@@ -1,3 +1,4 @@
1
+ import { ApiError } from '../client.js';
1
2
  const STEP_GUIDANCE = {
2
3
  app_created: 'Create your first app with capivv_list_apps to see current apps, or create one in the Capivv dashboard.',
3
4
  entitlement_created: 'Define entitlements to control feature access. Entitlements are granted when users purchase products.',
@@ -11,7 +12,7 @@ const STEP_GUIDANCE = {
11
12
  };
12
13
  export function registerStatusTool(server, client) {
13
14
  server.tool('capivv_status', 'Get the current status of your Capivv subscription platform including setup progress, resource counts, and key metrics.', async () => {
14
- const [apps, products, entitlements, offerings, rules, onboarding, analytics] = await Promise.allSettled([
15
+ const settled = await Promise.allSettled([
15
16
  client.listApps(),
16
17
  client.listProducts(),
17
18
  client.listEntitlements(),
@@ -20,6 +21,26 @@ export function registerStatusTool(server, client) {
20
21
  client.getOnboarding(),
21
22
  client.getAnalyticsOverview(),
22
23
  ]);
24
+ const [apps, products, entitlements, offerings, rules, onboarding, analytics] = settled;
25
+ // Every call goes through the same Bearer-token auth. If even one returns
26
+ // 401, the bearer is bad and every count we'd report would be a lie —
27
+ // short-circuit with a clear message instead of rendering zeros.
28
+ const authFailure = settled.find((r) => r.status === 'rejected' && r.reason instanceof ApiError && r.reason.status === 401);
29
+ if (authFailure) {
30
+ return {
31
+ isError: true,
32
+ content: [
33
+ {
34
+ type: 'text',
35
+ text: 'Authentication failed (HTTP 401). Verify CAPIVV_API_KEY (or the Bearer token your MCP client is sending) is a valid sk_* secret key.',
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ const labels = ['apps', 'products', 'entitlements', 'offerings', 'rules', 'onboarding', 'analytics'];
41
+ const warnings = settled
42
+ .map((r, i) => (r.status === 'rejected' ? `${labels[i]}: ${r.reason.message}` : null))
43
+ .filter((w) => w !== null);
23
44
  const count = (r) => r.status === 'fulfilled' ? r.value.length : 0;
24
45
  const result = {
25
46
  resources: {
@@ -30,6 +51,9 @@ export function registerStatusTool(server, client) {
30
51
  rules: count(rules),
31
52
  },
32
53
  };
54
+ if (warnings.length) {
55
+ result._warnings = warnings;
56
+ }
33
57
  if (onboarding.status === 'fulfilled') {
34
58
  const ob = onboarding.value;
35
59
  result.onboarding = {
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerStopExperimentTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ export function registerStopExperimentTool(server, client) {
3
+ server.tool('capivv_stop_experiment', 'Mark an experiment as completed. New users stop being assigned to variants; existing assignments are preserved for analysis. Use capivv_get_experiment_summary to read final results.', {
4
+ experiment_id: z.string().describe('Experiment ID to stop'),
5
+ }, async ({ experiment_id }) => {
6
+ const exp = await client.stopExperiment(experiment_id);
7
+ return { content: [{ type: 'text', text: JSON.stringify(exp, null, 2) }] };
8
+ });
9
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerSyncSuggestionsCountTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,6 @@
1
+ export function registerSyncSuggestionsCountTool(server, client) {
2
+ server.tool('capivv_sync_suggestions_count', 'Get the count of pending drift suggestions across the workspace. A quick health check before deciding whether to call capivv_check_drift for full details.', async () => {
3
+ const count = await client.getSyncSuggestionsCount();
4
+ return { content: [{ type: 'text', text: JSON.stringify(count, null, 2) }] };
5
+ });
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerTriggerSyncTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,6 @@
1
+ export function registerTriggerSyncTool(server, client) {
2
+ server.tool('capivv_trigger_sync', 'Force an immediate sync run for every connected store integration in the workspace. Useful right after creating products in App Store Connect or Google Play to surface them as drift suggestions without waiting for the worker\'s scheduled interval.', async () => {
3
+ const result = await client.triggerSync();
4
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
5
+ });
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateAppTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateAppTool(server, client) {
3
+ server.tool('capivv_update_app', 'Update an app by ID. Provide only the fields you want to change. Common uses: rename, fix a placeholder bundle_id, edit settings.', {
4
+ app_id: z.string().describe('App ID'),
5
+ name: z.string().optional().describe('New display name'),
6
+ bundle_id: z
7
+ .string()
8
+ .optional()
9
+ .describe('New bundle ID (use to fix a placeholder like com.example.myapp)'),
10
+ settings: z
11
+ .record(z.string(), z.unknown())
12
+ .optional()
13
+ .describe('Free-form settings JSON (merged into existing)'),
14
+ }, async (args) => {
15
+ const { app_id, ...rest } = args;
16
+ const app = await client.updateApp(app_id, rest);
17
+ return { content: [{ type: 'text', text: JSON.stringify(app, null, 2) }] };
18
+ });
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateEntitlementTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateEntitlementTool(server, client) {
3
+ server.tool('capivv_update_entitlement', 'Update an entitlement by ID. Provide only the fields you want to change.', {
4
+ entitlement_id: z.string().describe('Entitlement ID'),
5
+ identifier: z.string().optional().describe('New stable identifier'),
6
+ display_name: z.string().optional().describe('New display name'),
7
+ description: z.string().optional().describe('New description'),
8
+ }, async (args) => {
9
+ const { entitlement_id, ...rest } = args;
10
+ const ent = await client.updateEntitlement(entitlement_id, rest);
11
+ return { content: [{ type: 'text', text: JSON.stringify(ent, null, 2) }] };
12
+ });
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateExperimentTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateExperimentTool(server, client) {
3
+ server.tool('capivv_update_experiment', 'Update an experiment by ID. Provide only the fields you want to change. Use capivv_start_experiment / capivv_stop_experiment for status flips so audit logs and assignment freezing fire correctly.', {
4
+ experiment_id: z.string().describe('Experiment ID to update'),
5
+ name: z.string().optional().describe('New display name'),
6
+ description: z.string().optional().describe('New description'),
7
+ status: z
8
+ .string()
9
+ .optional()
10
+ .describe('New status (draft / running / paused / completed). Prefer start/stop tools.'),
11
+ target_metric: z.string().optional().describe('New target metric'),
12
+ start_date: z.string().optional().describe('New start timestamp (ISO 8601)'),
13
+ end_date: z.string().optional().describe('New end timestamp (ISO 8601)'),
14
+ sample_size_target: z.number().optional().describe('New sample size target'),
15
+ confidence_level: z.number().optional().describe('New confidence level'),
16
+ metadata: z.record(z.string(), z.unknown()).optional().describe('Free-form metadata'),
17
+ }, async (args) => {
18
+ const { experiment_id, ...rest } = args;
19
+ const exp = await client.updateExperiment(experiment_id, rest);
20
+ return { content: [{ type: 'text', text: JSON.stringify(exp, null, 2) }] };
21
+ });
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateOfferingTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateOfferingTool(server, client) {
3
+ server.tool('capivv_update_offering', 'Update an offering by ID. Provide only the fields you want to change. Pass `packages` to replace the package list (full replacement, not merge — include every package you want to keep).', {
4
+ offering_id: z.string().describe('Offering ID'),
5
+ display_name: z.string().optional().describe('New display name'),
6
+ description: z.string().optional().describe('New description'),
7
+ is_default: z
8
+ .boolean()
9
+ .optional()
10
+ .describe('Whether this is the default offering for its app'),
11
+ packages: z
12
+ .array(z.object({
13
+ identifier: z.string(),
14
+ product_id: z.string(),
15
+ display_name: z.string(),
16
+ description: z.string().optional(),
17
+ package_type: z.string().describe('monthly | annual | weekly | lifetime | custom'),
18
+ position: z.number(),
19
+ }))
20
+ .optional()
21
+ .describe('Replacement package list (full replacement, not merge)'),
22
+ metadata: z
23
+ .record(z.string(), z.unknown())
24
+ .optional()
25
+ .describe('Free-form metadata (replaces existing)'),
26
+ }, async (args) => {
27
+ const { offering_id, ...rest } = args;
28
+ const offering = await client.updateOffering(offering_id, rest);
29
+ return { content: [{ type: 'text', text: JSON.stringify(offering, null, 2) }] };
30
+ });
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdatePaywallTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdatePaywallTool(server, client) {
3
+ server.tool('capivv_update_paywall', 'Update a paywall by ID. Provide only the fields you want to change. Use this to attach a template, swap the config blob, or update metadata. Use capivv_list_paywalls to discover the paywall_id.', {
4
+ paywall_id: z.string().describe('Paywall ID to update'),
5
+ name: z.string().optional().describe('New display name'),
6
+ template: z
7
+ .string()
8
+ .optional()
9
+ .describe('Template identifier (e.g. "default", "minimal", "image_first")'),
10
+ config: z
11
+ .record(z.string(), z.unknown())
12
+ .optional()
13
+ .describe('Paywall configuration JSON (layout, copy, products to display, etc.)'),
14
+ metadata: z
15
+ .record(z.string(), z.unknown())
16
+ .optional()
17
+ .describe('Free-form metadata stored alongside the paywall'),
18
+ }, async (args) => {
19
+ const { paywall_id, ...rest } = args;
20
+ const paywall = await client.updatePaywall(paywall_id, rest);
21
+ return { content: [{ type: 'text', text: JSON.stringify(paywall, null, 2) }] };
22
+ });
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateProductTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateProductTool(server, client) {
3
+ server.tool('capivv_update_product', 'Update an existing product. Only provided fields are changed.', {
4
+ product_id: z.string().describe('Product ID to update'),
5
+ external_id: z.string().optional().describe('New store product ID'),
6
+ display_name: z.string().optional().describe('New display name'),
7
+ description: z.string().optional().describe('New description'),
8
+ entitlement_ids: z
9
+ .array(z.string())
10
+ .optional()
11
+ .describe('New entitlement IDs (replaces existing)'),
12
+ }, async ({ product_id, ...updates }) => {
13
+ const product = await client.updateProduct(product_id, updates);
14
+ return { content: [{ type: 'text', text: JSON.stringify(product, null, 2) }] };
15
+ });
16
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdatePromotionTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdatePromotionTool(server, client) {
3
+ server.tool('capivv_update_promotion', 'Update a promotion by ID. Provide only the fields you want to change. Use capivv_list_promotions to discover the promotion_id.', {
4
+ promotion_id: z.string().describe('Promotion ID to update'),
5
+ name: z.string().optional().describe('New display name'),
6
+ description: z.string().optional().describe('New description'),
7
+ discount_percent: z
8
+ .number()
9
+ .optional()
10
+ .describe('New percentage discount (0-100)'),
11
+ max_redemptions: z
12
+ .number()
13
+ .optional()
14
+ .describe('New redemption cap. Reduce only — increasing past current_redemptions is a no-op.'),
15
+ }, async (args) => {
16
+ const { promotion_id, ...rest } = args;
17
+ const promotion = await client.updatePromotion(promotion_id, rest);
18
+ return { content: [{ type: 'text', text: JSON.stringify(promotion, null, 2) }] };
19
+ });
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerUpdateRescueFlowTool(server: McpServer, client: CapivvClient): void;
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ export function registerUpdateRescueFlowTool(server, client) {
3
+ server.tool('capivv_update_rescue_flow', [
4
+ 'Update a rescue flow by ID. Provide only the fields you want to change.',
5
+ '`conditions` is an eligibility filter object (e.g. { product_ids: [...], min_tenure_days: 30 }).',
6
+ '`steps` is the offer ladder shown to the user during cancellation (e.g. [{ type: "discount", percent: 50 }, { type: "pause", months: 1 }]).',
7
+ 'Use capivv_list_rescue_flows to discover the rescue_flow_id.',
8
+ ].join(' '), {
9
+ rescue_flow_id: z.string().describe('Rescue flow ID to update'),
10
+ name: z.string().optional().describe('New display name'),
11
+ description: z.string().optional().describe('New description'),
12
+ priority: z.number().optional().describe('New priority'),
13
+ conditions: z
14
+ .record(z.string(), z.unknown())
15
+ .optional()
16
+ .describe('Eligibility filter object'),
17
+ steps: z
18
+ .record(z.string(), z.unknown())
19
+ .optional()
20
+ .describe('Offer ladder configuration'),
21
+ metadata: z.record(z.string(), z.unknown()).optional().describe('Free-form metadata'),
22
+ }, async (args) => {
23
+ const { rescue_flow_id, ...rest } = args;
24
+ const flow = await client.updateRescueFlow(rescue_flow_id, rest);
25
+ return { content: [{ type: 'text', text: JSON.stringify(flow, null, 2) }] };
26
+ });
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { CapivvClient } from '../client.js';
3
+ export declare function registerVerifySetupTool(server: McpServer, client: CapivvClient): void;