@capivv/mcp-server 0.5.26 → 0.5.39
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/dist/client.d.ts +27 -2
- package/dist/client.js +31 -0
- package/dist/tools/attach-products-to-review-submission.d.ts +3 -0
- package/dist/tools/attach-products-to-review-submission.js +27 -0
- package/dist/tools/debug-apple-sync.d.ts +3 -0
- package/dist/tools/debug-apple-sync.js +22 -0
- package/dist/tools/delete-user.d.ts +3 -0
- package/dist/tools/delete-user.js +27 -0
- package/dist/tools/drift-report.d.ts +3 -0
- package/dist/tools/drift-report.js +28 -0
- package/dist/tools/index.js +27 -0
- package/dist/tools/set-app-legal-urls.d.ts +3 -0
- package/dist/tools/set-app-legal-urls.js +36 -0
- package/dist/tools/update-product.js +30 -0
- package/dist/tools/update-variant.d.ts +14 -0
- package/dist/tools/update-variant.js +37 -0
- package/dist/types.d.ts +102 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Config } from './config.js';
|
|
2
|
-
import type { App, Product, Entitlement, Offering, Rule, Experiment, ExperimentSummary, AnalyticsOverview, OnboardingResponse, ImportPreviewResponse, CreateAppRequest, CreateEntitlementRequest, CreateProductRequest, CreateOfferingRequest, CreateRuleRequest, ValidateRuleRequest, ValidateRuleResponse, WhoamiResponse, ApiKeyUsageResponse, Paywall, CreatePaywallRequest, UpdatePaywallRequest, PaywallStats, Promotion, CreatePromotionRequest, UpdatePromotionRequest, RescueFlow, CreateRescueFlowRequest, UpdateRescueFlowRequest, RescueStats, PricingStrategy, CreatePricingStrategyRequest, PricingPreviewResult, PricingRecomputeRequest, PricingRecomputeResult, PricingPushResult, PricingChangeRequest, SetCountryPriceOverrideRequest, ExperimentWithVariants, CreateExperimentRequest, UpdateExperimentRequest, UpdateAppRequest, UpdateEntitlementRequest, UpdateOfferingRequest, AutopilotRunResult, SyncSuggestion, SyncSuggestionsCount, TriggerSyncResult, IntegrationSummary, ConnectAppleIntegrationRequest, ConnectAppleIntegrationResult, ConnectGoogleIntegrationRequest, ConnectGoogleIntegrationResult, SetSubscriptionReviewScreenshotRequest, SetSubscriptionReviewScreenshotResult, SubscriptionStateInfo, TouchSubscriptionResult, ProbeSubmissionRequest, ProbeSubmissionResult, SetAppPrivacyUrlRequest, SetAppPrivacyUrlResult, SetIapReviewScreenshotRequest, SetIapReviewScreenshotResult, IapStateInfo, ListAppleSubscriptionsResult, ListAppleIapsResult, DeleteAppleResult, ListAppUsersResult, FindUserResult, GrantEntitlementRequest, GrantEntitlementResult, RevokeEntitlementRequest, SetAppleSubscriptionLevelRequest, SetAppleSubscriptionLevelResult, SetReviewScreenshotRequest, SetReviewScreenshotResult } from './types.js';
|
|
2
|
+
import type { App, Product, Entitlement, Offering, Rule, Experiment, ExperimentSummary, AnalyticsOverview, OnboardingResponse, ImportPreviewResponse, CreateAppRequest, CreateEntitlementRequest, CreateProductRequest, CreateOfferingRequest, CreateRuleRequest, ValidateRuleRequest, ValidateRuleResponse, WhoamiResponse, ApiKeyUsageResponse, Paywall, CreatePaywallRequest, UpdatePaywallRequest, PaywallStats, Promotion, CreatePromotionRequest, UpdatePromotionRequest, RescueFlow, CreateRescueFlowRequest, UpdateRescueFlowRequest, RescueStats, PricingStrategy, CreatePricingStrategyRequest, PricingPreviewResult, PricingRecomputeRequest, PricingRecomputeResult, PricingPushResult, PricingChangeRequest, SetCountryPriceOverrideRequest, ExperimentWithVariants, CreateExperimentRequest, UpdateExperimentRequest, UpdateAppRequest, UpdateEntitlementRequest, UpdateOfferingRequest, AutopilotRunResult, SyncSuggestion, SyncSuggestionsCount, TriggerSyncResult, IntegrationSummary, ConnectAppleIntegrationRequest, ConnectAppleIntegrationResult, ConnectGoogleIntegrationRequest, ConnectGoogleIntegrationResult, SetSubscriptionReviewScreenshotRequest, SetSubscriptionReviewScreenshotResult, SubscriptionStateInfo, TouchSubscriptionResult, ProbeSubmissionRequest, ProbeSubmissionResult, SetAppPrivacyUrlRequest, SetAppPrivacyUrlResult, SetIapReviewScreenshotRequest, SetIapReviewScreenshotResult, IapStateInfo, ListAppleSubscriptionsResult, ListAppleIapsResult, DeleteAppleResult, ListAppUsersResult, FindUserResult, GrantEntitlementRequest, GrantEntitlementResult, RevokeEntitlementRequest, SetAppleSubscriptionLevelRequest, SetAppleSubscriptionLevelResult, SetReviewScreenshotRequest, SetReviewScreenshotResult, DeleteAppUserRequest, DeleteAppUserResult, AttachToReviewSubmissionRequest, AttachToReviewSubmissionResult, SetAppLegalUrlsRequest, SetAppLegalUrlsResult, DriftReportRequest, DriftReportResult, DebugAppleSyncRequest, DebugAppleSyncResult } from './types.js';
|
|
3
3
|
export declare class ApiError extends Error {
|
|
4
4
|
status: number;
|
|
5
5
|
code: string;
|
|
@@ -35,7 +35,18 @@ export declare class CapivvClient {
|
|
|
35
35
|
createProduct(data: CreateProductRequest): Promise<Product>;
|
|
36
36
|
private patch;
|
|
37
37
|
private delete;
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* V0.5.34 — widened to allow `pricing_strategy_id` (issue #13 gap 1). The
|
|
40
|
+
* backend now binds the strategy to the product when this is set; pre-v0.5.34
|
|
41
|
+
* there was no API path to do it. `pricing_strategy_id` is intentionally
|
|
42
|
+
* not on CreateProductRequest because the field is only mutable post-create.
|
|
43
|
+
*/
|
|
44
|
+
updateProduct(id: string, data: Partial<Omit<CreateProductRequest, 'app_id'>> & {
|
|
45
|
+
pricing_strategy_id?: string;
|
|
46
|
+
base_amount_cents?: number;
|
|
47
|
+
base_currency?: string;
|
|
48
|
+
base_country_code?: string;
|
|
49
|
+
}): Promise<Product>;
|
|
39
50
|
deleteProduct(id: string): Promise<void>;
|
|
40
51
|
deleteRule(id: string): Promise<void>;
|
|
41
52
|
activateRule(id: string): Promise<Rule>;
|
|
@@ -65,6 +76,15 @@ export declare class CapivvClient {
|
|
|
65
76
|
createExperiment(data: CreateExperimentRequest): Promise<ExperimentWithVariants>;
|
|
66
77
|
getExperiment(id: string): Promise<ExperimentWithVariants>;
|
|
67
78
|
updateExperiment(id: string, data: UpdateExperimentRequest): Promise<ExperimentWithVariants>;
|
|
79
|
+
/**
|
|
80
|
+
* V0.5.36 — patch a single variant's name / traffic_percent / config.
|
|
81
|
+
* Issue #15.
|
|
82
|
+
*/
|
|
83
|
+
updateExperimentVariant(experimentId: string, variantId: string, data: {
|
|
84
|
+
name?: string;
|
|
85
|
+
traffic_percent?: number;
|
|
86
|
+
config?: Record<string, unknown>;
|
|
87
|
+
}): Promise<unknown>;
|
|
68
88
|
startExperiment(id: string): Promise<ExperimentWithVariants>;
|
|
69
89
|
stopExperiment(id: string): Promise<ExperimentWithVariants>;
|
|
70
90
|
getApp(id: string): Promise<App>;
|
|
@@ -110,4 +130,9 @@ export declare class CapivvClient {
|
|
|
110
130
|
}>;
|
|
111
131
|
setAppleSubscriptionLevel(req: SetAppleSubscriptionLevelRequest): Promise<SetAppleSubscriptionLevelResult>;
|
|
112
132
|
setReviewScreenshot(req: SetReviewScreenshotRequest): Promise<SetReviewScreenshotResult>;
|
|
133
|
+
deleteAppUser(req: DeleteAppUserRequest): Promise<DeleteAppUserResult>;
|
|
134
|
+
attachProductsToReviewSubmission(req: AttachToReviewSubmissionRequest): Promise<AttachToReviewSubmissionResult>;
|
|
135
|
+
setAppLegalUrls(req: SetAppLegalUrlsRequest): Promise<SetAppLegalUrlsResult>;
|
|
136
|
+
driftReport(req?: DriftReportRequest): Promise<DriftReportResult>;
|
|
137
|
+
debugAppleSync(req: DebugAppleSyncRequest): Promise<DebugAppleSyncResult>;
|
|
113
138
|
}
|
package/dist/client.js
CHANGED
|
@@ -135,6 +135,12 @@ export class CapivvClient {
|
|
|
135
135
|
delete(path) {
|
|
136
136
|
return this.request('DELETE', path);
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* V0.5.34 — widened to allow `pricing_strategy_id` (issue #13 gap 1). The
|
|
140
|
+
* backend now binds the strategy to the product when this is set; pre-v0.5.34
|
|
141
|
+
* there was no API path to do it. `pricing_strategy_id` is intentionally
|
|
142
|
+
* not on CreateProductRequest because the field is only mutable post-create.
|
|
143
|
+
*/
|
|
138
144
|
async updateProduct(id, data) {
|
|
139
145
|
return this.patch(`/dashboard/products/${encodeURIComponent(id)}`, data);
|
|
140
146
|
}
|
|
@@ -245,6 +251,13 @@ export class CapivvClient {
|
|
|
245
251
|
async updateExperiment(id, data) {
|
|
246
252
|
return this.patch(`/dashboard/experiments/${encodeURIComponent(id)}`, data);
|
|
247
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* V0.5.36 — patch a single variant's name / traffic_percent / config.
|
|
256
|
+
* Issue #15.
|
|
257
|
+
*/
|
|
258
|
+
async updateExperimentVariant(experimentId, variantId, data) {
|
|
259
|
+
return this.patch(`/dashboard/experiments/${encodeURIComponent(experimentId)}/variants/${encodeURIComponent(variantId)}`, data);
|
|
260
|
+
}
|
|
248
261
|
async startExperiment(id) {
|
|
249
262
|
return this.post(`/dashboard/experiments/${encodeURIComponent(id)}/start`);
|
|
250
263
|
}
|
|
@@ -401,4 +414,22 @@ export class CapivvClient {
|
|
|
401
414
|
const { external_id, ...body } = req;
|
|
402
415
|
return this.post(`/dashboard/products/by-external-id/${encodeURIComponent(external_id)}/review-screenshot`, body);
|
|
403
416
|
}
|
|
417
|
+
async deleteAppUser(req) {
|
|
418
|
+
const { user_id, ...body } = req;
|
|
419
|
+
return this.post(`/dashboard/users/${encodeURIComponent(user_id)}/delete`, body);
|
|
420
|
+
}
|
|
421
|
+
async attachProductsToReviewSubmission(req) {
|
|
422
|
+
const { app_id, ...body } = req;
|
|
423
|
+
return this.post(`/dashboard/apps/${encodeURIComponent(app_id)}/apple/attach-to-review-submission`, body);
|
|
424
|
+
}
|
|
425
|
+
async setAppLegalUrls(req) {
|
|
426
|
+
const { app_id, ...body } = req;
|
|
427
|
+
return this.post(`/dashboard/apps/${encodeURIComponent(app_id)}/legal-urls`, body);
|
|
428
|
+
}
|
|
429
|
+
async driftReport(req = {}) {
|
|
430
|
+
return this.post('/dashboard/drift-report', req);
|
|
431
|
+
}
|
|
432
|
+
async debugAppleSync(req) {
|
|
433
|
+
return this.post(`/dashboard/apps/${encodeURIComponent(req.app_id)}/apple/debug-sync`);
|
|
434
|
+
}
|
|
404
435
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerAttachProductsToReviewSubmissionTool(server, client) {
|
|
3
|
+
server.tool('capivv_attach_products_to_version', [
|
|
4
|
+
"Attach subscriptions and in-app purchases to an Apple App Store version submission so they ship with the app version's review.",
|
|
5
|
+
'',
|
|
6
|
+
"Apple requires the first IAPs / subs on a new app to be explicitly attached to the v1.0 version submission via App Store Connect's \"In-App Purchases and Subscriptions\" section. Without this attachment the version submits but the products aren't reviewed, and Apple rejects under guideline 2.1(b) — caught by Interrupt's v1.0 first rejection.",
|
|
7
|
+
'',
|
|
8
|
+
"This tool POSTs `/v1/reviewSubmissionItems` on Apple's side for each product. Capivv resolves the right relationship type per product (subscription vs in-app purchase) from its DB. Mix subs and IAPs freely in the same call — they all attach to the same submission.",
|
|
9
|
+
'',
|
|
10
|
+
"By default Capivv picks the latest `READY_FOR_REVIEW` review submission for the app (the one developers can still modify before submitting). Pass `apple_review_submission_id` to target a specific submission.",
|
|
11
|
+
'',
|
|
12
|
+
"Per-product errors don't abort the batch — each product reports success/failure independently. 409 from Apple (item already attached) is treated as success for retry idempotency.",
|
|
13
|
+
].join(' '), {
|
|
14
|
+
app_id: z.string().describe('Capivv app UUID.'),
|
|
15
|
+
external_ids: z
|
|
16
|
+
.array(z.string())
|
|
17
|
+
.min(1)
|
|
18
|
+
.describe('List of Capivv product external_ids to attach. Mix subscriptions and IAPs freely. Each one Capivv resolves to the right Apple numeric id + relationship type.'),
|
|
19
|
+
apple_review_submission_id: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Override the auto-pick. Default: the app's latest READY_FOR_REVIEW submission."),
|
|
23
|
+
}, async (args) => {
|
|
24
|
+
const result = await client.attachProductsToReviewSubmission(args);
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerDebugAppleSyncTool(server, client) {
|
|
3
|
+
server.tool('capivv_debug_apple_sync', [
|
|
4
|
+
"Diagnostic tool that runs the **exact** three App Store Connect API calls Capivv's sync worker makes, and returns the raw HTTP status + response body for each. Use this when sync fails repeatedly and the normal error_message isn't actionable enough.",
|
|
5
|
+
'',
|
|
6
|
+
'Performs sequentially:',
|
|
7
|
+
"1. `GET /v1/apps?filter[bundleId]={bundle_id}` — resolve Capivv's app bundle_id to Apple's numeric app id",
|
|
8
|
+
'2. `GET /v1/apps/{apple_app_id}/inAppPurchasesV2?limit=100` — list IAPs',
|
|
9
|
+
'3. `GET /v1/apps/{apple_app_id}/subscriptionGroups?limit=100` — list subscription groups',
|
|
10
|
+
'',
|
|
11
|
+
'Each step reports `{ url, status, body }` — the body is Apple\'s actual response (success or error), preserved verbatim up to 8KB. If a step fails, paste the response output back into the GitHub issue and we can pinpoint exactly which Apple endpoint is rejecting and why.',
|
|
12
|
+
'',
|
|
13
|
+
'Filed against issue #3 (ASC sync 500s) after v0.5.25\'s error-surfacing fix didn\'t solve the root cause for the customer — this tool gives ground truth without needing to dig through prod server logs.',
|
|
14
|
+
].join(' '), {
|
|
15
|
+
app_id: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe('Capivv app UUID. Get this from capivv_list_apps. Used to look up bundle_id + the workspace\'s connected Apple credentials.'),
|
|
18
|
+
}, async (args) => {
|
|
19
|
+
const result = await client.debugAppleSync(args);
|
|
20
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerDeleteUserTool(server, client) {
|
|
3
|
+
server.tool('capivv_delete_user', [
|
|
4
|
+
'Delete or anonymize a user from Capivv. Use for GDPR / CCPA right-to-erasure requests, customer-initiated account deletion (Apple 5.1.1(v)), or developer admin cleanup.',
|
|
5
|
+
'',
|
|
6
|
+
'**Default is anonymize, not hard delete.** Anonymize replaces `external_id` with `deleted-<uuid>` (still unique, no longer identifying), clears email, and empties attributes. Owned rows (entitlements, purchases) stay linked so Apple/Google server-to-server notifications for an active subscription still route correctly — they just point at an opaque row no longer tied to a real person. This is the recommended option for GDPR: the data is no longer identifying, which satisfies erasure.',
|
|
7
|
+
'',
|
|
8
|
+
'**Hard delete** (pass `hard_delete: true`) DROPs the row and cascade-deletes all owned data (entitlements, purchases, sessions, cancellation intents, experiment assignments). Analytics tables that reference the user (conversion funnel, paywall events, rescue events) keep aggregate rows with the user reference NULLed out. Use when the customer specifically asks for full deletion rather than anonymization.',
|
|
9
|
+
'',
|
|
10
|
+
"Logs the operation to the audit log with the supplied `reason` — required for GDPR compliance auditors (must be able to show evidence the erasure request was honoured). Returns a `completed_at` timestamp the caller can keep as a delete receipt.",
|
|
11
|
+
'',
|
|
12
|
+
'Idempotent: a second call on an already-deleted user returns `{ success: false }` without erroring.',
|
|
13
|
+
].join(' '), {
|
|
14
|
+
user_id: z.string().describe('Capivv user UUID. Use capivv_find_user to resolve from email/external_id.'),
|
|
15
|
+
hard_delete: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Default false (anonymize). Set true for full DELETE + cascade. Prefer anonymize unless the customer specifically requires complete row removal.'),
|
|
19
|
+
reason: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Free-form audit reason ("customer_initiated", "gdpr_erasure", "ccpa_erasure", "developer_admin"). Recorded with the delete operation.'),
|
|
23
|
+
}, async (args) => {
|
|
24
|
+
const result = await client.deleteAppUser(args);
|
|
25
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerDriftReportTool(server, client) {
|
|
3
|
+
server.tool('capivv_drift_report', [
|
|
4
|
+
'Capture a snapshot of the current setup (counts of products / entitlements / offerings / paywalls + pending drift grouped by suggestion type) and diff it against the previous snapshot in the same scope. Returns deltas + a markdown report ready to paste into a GitHub issue, Slack message, or email digest.',
|
|
5
|
+
'',
|
|
6
|
+
'Use this on a weekly cadence (via your own cron / GitHub Action / Workflow) to track week-over-week changes in Capivv\'s view of your apps. The diff highlights:',
|
|
7
|
+
'- New drift items since last run (positive `pending_drift_delta`)',
|
|
8
|
+
'- Resolved drift since last run (negative `pending_drift_delta`)',
|
|
9
|
+
'- Per-type drift movement (`drift_by_type_delta`)',
|
|
10
|
+
'- Growth or removal of products / entitlements / offerings / paywalls',
|
|
11
|
+
'',
|
|
12
|
+
"First-run returns empty deltas and the markdown notes \"no week-over-week comparison yet\" — subsequent runs compare against the most recent prior snapshot in the same scope (`app_id` filter or tenant-wide).",
|
|
13
|
+
'',
|
|
14
|
+
"Set `save: false` for ad-hoc inspection without polluting the historical series; default true (the weekly-cron workflow needs the row persisted to compare against next week).",
|
|
15
|
+
].join(' '), {
|
|
16
|
+
app_id: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Optional app filter. When omitted, snapshot + diff are tenant-wide. App-scoped and tenant-wide snapshots are stored in separate series — they do not interfere with each other.'),
|
|
20
|
+
save: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('When false, compute snapshot + diff but do not persist. Default true (week-over-week needs the row saved).'),
|
|
24
|
+
}, async (args) => {
|
|
25
|
+
const result = await client.driftReport(args);
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -47,6 +47,7 @@ import { registerApproveChangeRequestTool } from './approve-change-request.js';
|
|
|
47
47
|
import { registerSetCountryPriceOverrideTool } from './set-country-price-override.js';
|
|
48
48
|
import { registerCreateExperimentTool } from './create-experiment.js';
|
|
49
49
|
import { registerUpdateExperimentTool } from './update-experiment.js';
|
|
50
|
+
import { registerUpdateVariantTool } from './update-variant.js';
|
|
50
51
|
import { registerStartExperimentTool } from './start-experiment.js';
|
|
51
52
|
import { registerStopExperimentTool } from './stop-experiment.js';
|
|
52
53
|
import { registerGetExperimentSummaryTool } from './get-experiment-summary.js';
|
|
@@ -86,6 +87,11 @@ import { registerListUsersTool } from './list-users.js';
|
|
|
86
87
|
import { registerFindUserTool } from './find-user.js';
|
|
87
88
|
import { registerSetAppleSubscriptionLevelTool } from './set-apple-subscription-level.js';
|
|
88
89
|
import { registerSetReviewScreenshotTool } from './set-review-screenshot.js';
|
|
90
|
+
import { registerDeleteUserTool } from './delete-user.js';
|
|
91
|
+
import { registerAttachProductsToReviewSubmissionTool } from './attach-products-to-review-submission.js';
|
|
92
|
+
import { registerSetAppLegalUrlsTool } from './set-app-legal-urls.js';
|
|
93
|
+
import { registerDriftReportTool } from './drift-report.js';
|
|
94
|
+
import { registerDebugAppleSyncTool } from './debug-apple-sync.js';
|
|
89
95
|
import { registerListAppleSubscriptionsTool } from './list-apple-subscriptions.js';
|
|
90
96
|
import { registerListAppleIapsTool } from './list-apple-iaps.js';
|
|
91
97
|
import { registerDeleteAppleSubscriptionTool } from './delete-apple-subscription.js';
|
|
@@ -156,6 +162,7 @@ export function registerAllTools(server, client) {
|
|
|
156
162
|
// Experiment writes (V8 Phase B.5)
|
|
157
163
|
registerCreateExperimentTool(server, client);
|
|
158
164
|
registerUpdateExperimentTool(server, client);
|
|
165
|
+
registerUpdateVariantTool(server, client);
|
|
159
166
|
registerStartExperimentTool(server, client);
|
|
160
167
|
registerStopExperimentTool(server, client);
|
|
161
168
|
registerGetExperimentSummaryTool(server, client);
|
|
@@ -238,4 +245,24 @@ export function registerAllTools(server, client) {
|
|
|
238
245
|
// (set_subscription_review_screenshot / set_iap_review_screenshot)
|
|
239
246
|
// remain as escape hatches but their docstrings now redirect here.
|
|
240
247
|
registerSetReviewScreenshotTool(server, client);
|
|
248
|
+
// V0.5.27:
|
|
249
|
+
// - capivv_delete_user (issue #9 — GDPR / CCPA right-to-erasure)
|
|
250
|
+
// - capivv_attach_products_to_version (issue #8 Gap 2 — Apple 2.1(b)
|
|
251
|
+
// rejection prevention for first-version submissions with IAPs)
|
|
252
|
+
registerDeleteUserTool(server, client);
|
|
253
|
+
registerAttachProductsToReviewSubmissionTool(server, client);
|
|
254
|
+
// V0.5.28 — issue #6: App Store guideline 3.1.2(c) requires
|
|
255
|
+
// functional Terms + Privacy links on the paywall. Set the URLs
|
|
256
|
+
// once at the app level; the @capivv/capacitor-react `legalLinks`
|
|
257
|
+
// component renders them.
|
|
258
|
+
registerSetAppLegalUrlsTool(server, client);
|
|
259
|
+
// V0.5.29 — issue #5: weekly drift watch. Customers run this on
|
|
260
|
+
// their own cadence (cron / GitHub Action); Capivv stores the
|
|
261
|
+
// snapshot + returns the week-over-week diff.
|
|
262
|
+
registerDriftReportTool(server, client);
|
|
263
|
+
// V0.5.31 — issue #3 diagnostic: Apple sync still 500ing after
|
|
264
|
+
// v0.5.25's error-surfacing fix. This tool runs the exact three
|
|
265
|
+
// ASC calls the worker makes and returns raw response bodies so
|
|
266
|
+
// we can see Apple's actual error instead of guessing.
|
|
267
|
+
registerDebugAppleSyncTool(server, client);
|
|
241
268
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerSetAppLegalUrlsTool(server, client) {
|
|
3
|
+
server.tool('capivv_set_app_legal_urls', [
|
|
4
|
+
"Store the app's Privacy Policy + Terms of Use URLs at the Capivv app level. These power the `legalLinks` paywall component in @capivv/capacitor-react — the component reads URLs from the template props the caller passes, so the agent typically pipes these into the paywall template's legalLinks props on update_paywall.",
|
|
5
|
+
'',
|
|
6
|
+
"**Required by App Store guideline 3.1.2(c)**: every iOS app with auto-renewable subscriptions must display functional Terms + Privacy links on the paywall itself, not just static disclaimer text. Missing them = guaranteed App Review rejection (Interrupt v1.0 was rejected for exactly this).",
|
|
7
|
+
'',
|
|
8
|
+
"Both URLs must be HTTPS. Pass only the fields you want to change; omitting one leaves the existing value.",
|
|
9
|
+
'',
|
|
10
|
+
'Note: for the Apple App Store privacy field specifically (separate from the in-paywall legal links), use `capivv_set_app_privacy_url` which PATCHes Apple\'s appInfoLocalizations resource directly.',
|
|
11
|
+
].join(' '), {
|
|
12
|
+
app_id: z.string().describe('Capivv app UUID.'),
|
|
13
|
+
privacy_policy_url: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('HTTPS URL to the Privacy Policy page.'),
|
|
17
|
+
terms_policy_url: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('HTTPS URL to the Terms of Use / EULA page.'),
|
|
21
|
+
}, async (args) => {
|
|
22
|
+
if (!args.privacy_policy_url && !args.terms_policy_url) {
|
|
23
|
+
return {
|
|
24
|
+
isError: true,
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: 'Provide at least one of privacy_policy_url or terms_policy_url.',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const result = await client.setAppLegalUrls(args);
|
|
34
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -9,6 +9,36 @@ export function registerUpdateProductTool(server, client) {
|
|
|
9
9
|
.array(z.string())
|
|
10
10
|
.optional()
|
|
11
11
|
.describe('New entitlement IDs (replaces existing)'),
|
|
12
|
+
// V0.5.34 — bind a product to a pricing strategy (issue #13 gap 1).
|
|
13
|
+
// Pre-v0.5.34 there was no API path to do this; capivv_create_product
|
|
14
|
+
// also does not expose the field. Pass a strategy uuid to bind. To
|
|
15
|
+
// unbind, contact support (separate followup; the repo's COALESCE
|
|
16
|
+
// currently can't represent the unset case).
|
|
17
|
+
pricing_strategy_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.uuid()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Bind this product to a pricing strategy. The strategy must already exist (see capivv_create_pricing_strategy). After binding, capivv_preview_pricing and capivv_recompute_prices use it automatically.'),
|
|
22
|
+
// V0.5.36 — base price for pricing-strategy computation (issue #13
|
|
23
|
+
// followup). Without these set, preview_pricing returns "Base amount
|
|
24
|
+
// must be positive, got 0" because strategies read their base from
|
|
25
|
+
// these fields, not from the connected store's tier.
|
|
26
|
+
base_amount_cents: z
|
|
27
|
+
.number()
|
|
28
|
+
.int()
|
|
29
|
+
.positive()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Base price in cents (in base_currency). E.g. 999 = $9.99 USD. Required for pricing strategies to compute per-country tiers.'),
|
|
32
|
+
base_currency: z
|
|
33
|
+
.string()
|
|
34
|
+
.length(3)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('ISO 4217 base currency for base_amount_cents. E.g. "USD".'),
|
|
37
|
+
base_country_code: z
|
|
38
|
+
.string()
|
|
39
|
+
.length(2)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('ISO 3166-1 alpha-2 base country code. E.g. "US". Strategies use this as the reference territory when extrapolating to other countries.'),
|
|
12
42
|
}, async ({ product_id, ...updates }) => {
|
|
13
43
|
const product = await client.updateProduct(product_id, updates);
|
|
14
44
|
return { content: [{ type: 'text', text: JSON.stringify(product, null, 2) }] };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { CapivvClient } from '../client.js';
|
|
3
|
+
/**
|
|
4
|
+
* V0.5.36 — issue #15. Patch a single variant's name / traffic_percent /
|
|
5
|
+
* config after the experiment was created. Pre-v0.5.36 variants were
|
|
6
|
+
* immutable post-create — customers had to encode placeholder→real
|
|
7
|
+
* mapping in the experiment description.
|
|
8
|
+
*
|
|
9
|
+
* `traffic_percent` changes are rejected by the backend when the
|
|
10
|
+
* experiment is already `running` (would skew the bucket distribution
|
|
11
|
+
* mid-flight). Stop or pause first if you need to re-weight a live
|
|
12
|
+
* experiment.
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerUpdateVariantTool(server: McpServer, client: CapivvClient): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* V0.5.36 — issue #15. Patch a single variant's name / traffic_percent /
|
|
4
|
+
* config after the experiment was created. Pre-v0.5.36 variants were
|
|
5
|
+
* immutable post-create — customers had to encode placeholder→real
|
|
6
|
+
* mapping in the experiment description.
|
|
7
|
+
*
|
|
8
|
+
* `traffic_percent` changes are rejected by the backend when the
|
|
9
|
+
* experiment is already `running` (would skew the bucket distribution
|
|
10
|
+
* mid-flight). Stop or pause first if you need to re-weight a live
|
|
11
|
+
* experiment.
|
|
12
|
+
*/
|
|
13
|
+
export function registerUpdateVariantTool(server, client) {
|
|
14
|
+
server.tool('capivv_update_variant', 'Rename a variant, change its traffic split, or update its config JSON. Use this when an experiment was created with placeholder variant names (e.g. control / variant_a) and you want to give them descriptive names that match your application code paths.', {
|
|
15
|
+
experiment_id: z.string().uuid().describe('Parent experiment id'),
|
|
16
|
+
variant_id: z.string().uuid().describe('Variant id to update'),
|
|
17
|
+
name: z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('New variant name. Always safe to change, even on a running experiment. Use names that match your app-side branching (e.g. annual_default, monthly_default).'),
|
|
22
|
+
traffic_percent: z
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(0)
|
|
26
|
+
.max(100)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('New traffic share for this variant (0-100). The backend rejects this when the experiment is running to avoid skewing assignments mid-flight — stop or pause first.'),
|
|
29
|
+
config: z
|
|
30
|
+
.record(z.string(), z.unknown())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('New variant config JSON. Apps read this via Capivv.getVariantForExperiment to branch behavior. Server-side merge of this config into served paywall templates is tracked separately (capivv issue #16).'),
|
|
33
|
+
}, async ({ experiment_id, variant_id, ...updates }) => {
|
|
34
|
+
const updated = await client.updateExperimentVariant(experiment_id, variant_id, updates);
|
|
35
|
+
return { content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }] };
|
|
36
|
+
});
|
|
37
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -645,6 +645,13 @@ export interface ProbeSubmissionResult {
|
|
|
645
645
|
apple_product_id: string;
|
|
646
646
|
product_type: string;
|
|
647
647
|
submission_status: SubmissionProbeResult;
|
|
648
|
+
/**
|
|
649
|
+
* V0.5.28 — non-fatal warnings the agent should surface to the
|
|
650
|
+
* customer (independent of submission_status). Today's checks:
|
|
651
|
+
* subscription probe + app missing privacy/terms URLs → 3.1.2(c)
|
|
652
|
+
* rejection risk.
|
|
653
|
+
*/
|
|
654
|
+
warnings?: string[];
|
|
648
655
|
}
|
|
649
656
|
export interface SetAppPrivacyUrlRequest {
|
|
650
657
|
app_id: string;
|
|
@@ -735,6 +742,101 @@ export interface SetAppleSubscriptionLevelResult {
|
|
|
735
742
|
apple_subscription_id: string;
|
|
736
743
|
level: number;
|
|
737
744
|
}
|
|
745
|
+
export interface DebugAppleSyncRequest {
|
|
746
|
+
app_id: string;
|
|
747
|
+
}
|
|
748
|
+
export interface DebugSyncStep {
|
|
749
|
+
url: string;
|
|
750
|
+
status: number;
|
|
751
|
+
body: string;
|
|
752
|
+
}
|
|
753
|
+
export interface DebugAppleSyncResult {
|
|
754
|
+
app_id: string;
|
|
755
|
+
bundle_id: string;
|
|
756
|
+
result: {
|
|
757
|
+
bundle_id: string;
|
|
758
|
+
apple_app_id: string | null;
|
|
759
|
+
resolve_app: DebugSyncStep;
|
|
760
|
+
list_iaps: DebugSyncStep | null;
|
|
761
|
+
list_subscription_groups: DebugSyncStep | null;
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
export interface DriftReportRequest {
|
|
765
|
+
app_id?: string;
|
|
766
|
+
/** Default true. Set false for ad-hoc inspection without persisting a row. */
|
|
767
|
+
save?: boolean;
|
|
768
|
+
}
|
|
769
|
+
export interface DriftSnapshotPayload {
|
|
770
|
+
products_count: number;
|
|
771
|
+
entitlements_count: number;
|
|
772
|
+
offerings_count: number;
|
|
773
|
+
paywalls_count: number;
|
|
774
|
+
pending_drift_count: number;
|
|
775
|
+
drift_by_type: Record<string, number>;
|
|
776
|
+
}
|
|
777
|
+
export interface DriftSnapshotPayloadWithMeta {
|
|
778
|
+
taken_at: string;
|
|
779
|
+
payload: DriftSnapshotPayload;
|
|
780
|
+
}
|
|
781
|
+
export interface DriftDiff {
|
|
782
|
+
products_delta: number;
|
|
783
|
+
entitlements_delta: number;
|
|
784
|
+
offerings_delta: number;
|
|
785
|
+
paywalls_delta: number;
|
|
786
|
+
pending_drift_delta: number;
|
|
787
|
+
drift_by_type_delta: Record<string, number>;
|
|
788
|
+
}
|
|
789
|
+
export interface DriftReportResult {
|
|
790
|
+
current: DriftSnapshotPayload;
|
|
791
|
+
previous: DriftSnapshotPayloadWithMeta | null;
|
|
792
|
+
diff: DriftDiff;
|
|
793
|
+
/** Markdown rendering suitable for pasting into a GitHub issue or Slack message. */
|
|
794
|
+
markdown: string;
|
|
795
|
+
}
|
|
796
|
+
export interface SetAppLegalUrlsRequest {
|
|
797
|
+
app_id: string;
|
|
798
|
+
privacy_policy_url?: string;
|
|
799
|
+
terms_policy_url?: string;
|
|
800
|
+
}
|
|
801
|
+
export interface SetAppLegalUrlsResult {
|
|
802
|
+
app_id: string;
|
|
803
|
+
privacy_policy_url: string | null;
|
|
804
|
+
terms_policy_url: string | null;
|
|
805
|
+
}
|
|
806
|
+
export interface AttachToReviewSubmissionRequest {
|
|
807
|
+
app_id: string;
|
|
808
|
+
external_ids: string[];
|
|
809
|
+
/** Optional override; default picks the latest READY_FOR_REVIEW submission. */
|
|
810
|
+
apple_review_submission_id?: string;
|
|
811
|
+
}
|
|
812
|
+
export interface AttachedProduct {
|
|
813
|
+
external_id: string;
|
|
814
|
+
apple_product_id: string;
|
|
815
|
+
kind: 'subscription' | 'in_app_purchase';
|
|
816
|
+
}
|
|
817
|
+
export interface AttachFailure {
|
|
818
|
+
external_id: string;
|
|
819
|
+
error: string;
|
|
820
|
+
}
|
|
821
|
+
export interface AttachToReviewSubmissionResult {
|
|
822
|
+
apple_review_submission_id: string;
|
|
823
|
+
submission_state: string;
|
|
824
|
+
attached: AttachedProduct[];
|
|
825
|
+
failed: AttachFailure[];
|
|
826
|
+
}
|
|
827
|
+
export interface DeleteAppUserRequest {
|
|
828
|
+
user_id: string;
|
|
829
|
+
/** Default false (anonymize in place). Set true for full row deletion + cascade. */
|
|
830
|
+
hard_delete?: boolean;
|
|
831
|
+
reason?: string;
|
|
832
|
+
}
|
|
833
|
+
export interface DeleteAppUserResult {
|
|
834
|
+
user_id: string;
|
|
835
|
+
mode: 'anonymized' | 'hard_deleted';
|
|
836
|
+
success: boolean;
|
|
837
|
+
completed_at: string;
|
|
838
|
+
reason: string;
|
|
839
|
+
}
|
|
738
840
|
export interface SetReviewScreenshotRequest {
|
|
739
841
|
app_id: string;
|
|
740
842
|
external_id: string;
|