@attrkit/mcp 0.4.1 → 0.4.3

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 (2) hide show
  1. package/dist/cli.js +69 -24
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14059,7 +14059,12 @@ async function callApi(config, path, options = {}) {
14059
14059
  return {
14060
14060
  ok: false,
14061
14061
  status: 0,
14062
- json: { error: { code: "network_error", message: error instanceof Error ? error.message : String(error) } }
14062
+ json: {
14063
+ error: error instanceof Error && error.name === "AbortError" ? {
14064
+ code: "timeout",
14065
+ message: `no response within ${REQUEST_TIMEOUT_MS / 1000}s. The request may still have been applied on the server; retry with the same idempotency_key rather than a new one.`
14066
+ } : { code: "network_error", message: error instanceof Error ? error.message : String(error) }
14067
+ }
14063
14068
  };
14064
14069
  } finally {
14065
14070
  clearTimeout(timer);
@@ -14071,10 +14076,12 @@ function toToolResult(result) {
14071
14076
  const code = envelope?.error?.code ?? `http_${result.status}`;
14072
14077
  const message = envelope?.error?.message ? `: ${envelope.error.message}` : "";
14073
14078
  const retryAfter = result.status === 429 && result.retryAfter ? ` (Retry-After: ${result.retryAfter})` : "";
14079
+ const excerpt = !envelope?.error?.message && result.rawBody ? ` — body: ${result.rawBody.replace(/\s+/g, " ").trim().slice(0, 300)}` : "";
14080
+ const hint = result.status === 401 ? " Check ATTRKIT_API_KEY: this is the same error for a missing, wrong, revoked or expired key." : "";
14074
14081
  return {
14075
14082
  content: [{
14076
14083
  type: "text",
14077
- text: `AttrKit API error (${result.status || "network"}): ${code}${message}${retryAfter}`
14084
+ text: `AttrKit API error (${result.status || "network"}): ${code}${message}${retryAfter}${excerpt}${hint}`
14078
14085
  }],
14079
14086
  isError: true
14080
14087
  };
@@ -14103,16 +14110,16 @@ ${body}`
14103
14110
  structuredContent: structured
14104
14111
  };
14105
14112
  }
14106
- var SERVER_VERSION = "0.4.1";
14113
+ var SERVER_VERSION = "0.4.3";
14107
14114
  var periodSchema = z4.enum(["7d", "30d", "90d", "all"]).describe("Reporting window. Defaults to 30d.").optional();
14108
14115
  var appIdSchema = z4.string().describe("Optional app id to scope the result to one app.").optional();
14109
14116
  var requiredAppIdSchema = z4.string().describe("The AttrKit app id.");
14110
14117
  var idempotencyKeySchema = z4.string().max(255).describe("Stable retry key for this exact mutation.").optional();
14111
14118
  var ownersSchema = z4.record(z4.string(), z4.enum(["attrkit", "revenuecat", "customer"]));
14112
14119
  var spendRowSchema = z4.object({
14113
- campaign_id: z4.string().optional().describe("Required AttrKit campaign id. Preferred snake_case spelling."),
14120
+ campaign_id: z4.string().optional().describe("The AttrKit campaign UUID, NOT the ad network's own campaign id. It is the opposite of create_campaign_link.campaign_id and is the most common mistake here: read it from list_campaigns. The API rejects a network id by name. Preferred snake_case spelling."),
14114
14121
  campaignId: z4.string().optional().describe("Accepted camelCase alias for campaign_id."),
14115
- adset_id: z4.string().optional().nullable(),
14122
+ adset_id: z4.string().optional().nullable().describe("Ad set id. Must be sent together with ad_id; either one alone is rejected."),
14116
14123
  adsetId: z4.string().optional().nullable(),
14117
14124
  ad_id: z4.string().optional().nullable(),
14118
14125
  adId: z4.string().optional().nullable(),
@@ -14158,16 +14165,19 @@ function createServer(config, fetchImpl) {
14158
14165
  const server = new McpServer({ name: "attrkit", version: SERVER_VERSION });
14159
14166
  const call = (path, options) => callApi(config, path, { fetchImpl, ...options });
14160
14167
  server.registerTool("whoami", {
14168
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14161
14169
  title: "Inspect management-key scope",
14162
14170
  description: "Return the authenticated workspace, plan, app restriction and read/write scopes.",
14163
14171
  inputSchema: {}
14164
14172
  }, async () => toToolResult(await call("/api/v1/whoami")));
14165
14173
  server.registerTool("get_usage_cap", {
14174
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14166
14175
  title: "Get the workspace usage cap",
14167
14176
  description: "Return the workspace's monthly qualified-install cap, alert email, current billing period and usage. Requires a workspace-scoped read key.",
14168
14177
  inputSchema: {}
14169
14178
  }, async () => toToolResult(await call("/api/v1/workspace/usage-cap")));
14170
14179
  server.registerTool("set_usage_cap", {
14180
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14171
14181
  title: "Set the workspace usage cap",
14172
14182
  description: "Set a monthly qualified-install cap of at least 10,000 and its alert email. Pass null for both values to clear the cap. Requires a workspace-scoped write key.",
14173
14183
  inputSchema: {
@@ -14199,11 +14209,13 @@ function createServer(config, fetchImpl) {
14199
14209
  }));
14200
14210
  });
14201
14211
  server.registerTool("list_apps", {
14212
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14202
14213
  title: "List apps",
14203
14214
  description: "List the iOS and Android apps in your AttrKit workspace (id, name, platform, bundle id, store id, status).",
14204
14215
  inputSchema: {}
14205
14216
  }, async () => toToolResult(await call("/api/v1/apps")));
14206
14217
  server.registerTool("create_app", {
14218
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14207
14219
  title: "Create an app",
14208
14220
  description: "Create an iOS or Android app without the web UI. Returns the publishable key and a one-time app-scoped management key.",
14209
14221
  inputSchema: {
@@ -14236,11 +14248,13 @@ function createServer(config, fetchImpl) {
14236
14248
  }
14237
14249
  })));
14238
14250
  server.registerTool("get_app", {
14251
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14239
14252
  title: "Get an app",
14240
14253
  description: "Return one app's configuration.",
14241
14254
  inputSchema: { app_id: requiredAppIdSchema }
14242
14255
  }, async ({ app_id }) => toToolResult(await call(`/api/v1/apps/${encodeURIComponent(app_id)}`)));
14243
14256
  server.registerTool("update_app", {
14257
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14244
14258
  title: "Update an app",
14245
14259
  description: "Update app metadata, store destination, timezone or device-matching mode.",
14246
14260
  inputSchema: {
@@ -14275,6 +14289,7 @@ function createServer(config, fetchImpl) {
14275
14289
  }
14276
14290
  })));
14277
14291
  server.registerTool("archive_app", {
14292
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
14278
14293
  title: "Archive an app",
14279
14294
  description: "Archive an app and remove it from active customer operations.",
14280
14295
  inputSchema: { app_id: requiredAppIdSchema }
@@ -14282,11 +14297,13 @@ function createServer(config, fetchImpl) {
14282
14297
  method: "DELETE"
14283
14298
  })));
14284
14299
  server.registerTool("get_publishable_key", {
14300
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14285
14301
  title: "Get an app publishable key",
14286
14302
  description: "Reveal the recoverable SDK publishable key for an app.",
14287
14303
  inputSchema: { app_id: requiredAppIdSchema }
14288
14304
  }, async ({ app_id }) => toToolResult(await call(`/api/v1/apps/${encodeURIComponent(app_id)}/publishable-key`)));
14289
14305
  server.registerTool("rotate_publishable_key", {
14306
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
14290
14307
  title: "Rotate an app publishable key",
14291
14308
  description: "Issue a new SDK publishable key and return the old-key grace deadline.",
14292
14309
  inputSchema: {
@@ -14295,11 +14312,13 @@ function createServer(config, fetchImpl) {
14295
14312
  }
14296
14313
  }, async ({ app_id, idempotency_key }) => toToolResult(await call(`/api/v1/apps/${encodeURIComponent(app_id)}/publishable-key`, { method: "POST", idempotencyKey: idempotency_key })));
14297
14314
  server.registerTool("list_api_keys", {
14315
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14298
14316
  title: "List management keys",
14299
14317
  description: "List masked workspace and app management keys. Secret plaintext is never returned.",
14300
14318
  inputSchema: {}
14301
14319
  }, async () => toToolResult(await call("/api/v1/api-keys")));
14302
14320
  server.registerTool("create_api_key", {
14321
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14303
14322
  title: "Create a management key",
14304
14323
  description: "Create a read-only or read/write workspace or app key. The secret is returned once.",
14305
14324
  inputSchema: {
@@ -14314,6 +14333,7 @@ function createServer(config, fetchImpl) {
14314
14333
  body: { label, scopes, app_id: app_id ?? null }
14315
14334
  })));
14316
14335
  server.registerTool("revoke_api_key", {
14336
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
14317
14337
  title: "Revoke a management key",
14318
14338
  description: "Immediately revoke a management key by id.",
14319
14339
  inputSchema: { key_id: z4.string().uuid() }
@@ -14321,6 +14341,7 @@ function createServer(config, fetchImpl) {
14321
14341
  method: "DELETE"
14322
14342
  })));
14323
14343
  server.registerTool("list_links", {
14344
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14324
14345
  title: "List campaign links",
14325
14346
  description: "List tracking links, optionally for one app.",
14326
14347
  inputSchema: {
@@ -14336,11 +14357,13 @@ function createServer(config, fetchImpl) {
14336
14357
  }
14337
14358
  })));
14338
14359
  server.registerTool("list_campaigns", {
14360
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14339
14361
  title: "List campaigns",
14340
14362
  description: "List ad campaigns with their honest profit surface: the scale/cut/hold call, profit p05/p50/p95, spend, retention, and payback. Use this to see how campaigns are performing.",
14341
14363
  inputSchema: { app_id: appIdSchema, period: periodSchema }
14342
14364
  }, async ({ app_id, period }) => toToolResult(await call("/api/v1/reports/campaigns", { query: { appId: app_id, period } })));
14343
14365
  server.registerTool("list_ads", {
14366
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14344
14367
  title: "List creative-level ad performance",
14345
14368
  description: "Return ad and ad-set performance, evidence tiers, ROAS, CPA, CPC and modeled remainders.",
14346
14369
  inputSchema: {
@@ -14356,6 +14379,7 @@ function createServer(config, fetchImpl) {
14356
14379
  }
14357
14380
  })));
14358
14381
  server.registerTool("list_recent_events", {
14382
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14359
14383
  title: "List recent events and installs",
14360
14384
  description: "Return the same recent attribution events and installs visible on the Live dashboard.",
14361
14385
  inputSchema: {
@@ -14369,33 +14393,35 @@ function createServer(config, fetchImpl) {
14369
14393
  }
14370
14394
  })));
14371
14395
  server.registerTool("get_campaign_decision", {
14396
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14372
14397
  title: "Get campaign decisions (scale / cut / hold)",
14373
14398
  description: "Return AttrKit's profit DECISIONS for your campaigns: the scale/cut/hold call and confidence, computed by the attribution engine. Report the `call` VERBATIM — do not recompute or override it; when it says not_enough_data, say so. This is the tool to drive budget automation.",
14374
14399
  inputSchema: { app_id: appIdSchema }
14375
14400
  }, async ({ app_id }) => toToolResult(await call("/api/v1/decisions", { query: { appId: app_id } })));
14376
14401
  server.registerTool("record_campaign_decision", {
14402
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14377
14403
  title: "Record a campaign decision",
14378
14404
  description: "Freeze a scale/cut/hold decision and its exact model inputs for later maturity evaluation.",
14379
14405
  inputSchema: {
14380
14406
  app_id: requiredAppIdSchema,
14381
- campaign_id: z4.string(),
14382
- call: z4.enum(["scale", "cut", "hold"]),
14383
- actor: z4.enum(["dashboard", "auto_suggested"]).optional(),
14384
- p05: z4.number(),
14385
- p50: z4.number(),
14386
- p95: z4.number(),
14387
- spend_minor_units: z4.number().nonnegative(),
14388
- currency: z4.string().length(3),
14389
- band_width_pct: z4.number().nonnegative(),
14390
- threshold: z4.number(),
14391
- model_version: z4.string(),
14392
- cost_model_version: z4.number().int().positive().nullable(),
14393
- revenue_per_install_minor_units: z4.number().nonnegative().nullable(),
14407
+ campaign_id: z4.string().describe("The AttrKit campaign uuid, as returned by list_campaigns or get_campaign_decision. Not the ad network's own campaign id."),
14408
+ call: z4.enum(["scale", "cut", "hold"]).describe("The decision being frozen. Report get_campaign_decision's own call verbatim rather than forming your own."),
14409
+ actor: z4.enum(["dashboard", "auto_suggested"]).optional().describe("Who made the call. Use auto_suggested when an agent proposed it without a human confirming."),
14410
+ p05: z4.number().describe("Low end of the profit range from get_campaign_decision, in minor units. Pass through unchanged."),
14411
+ p50: z4.number().describe("Central profit estimate from get_campaign_decision, in minor units. Pass through unchanged."),
14412
+ p95: z4.number().describe("High end of the profit range from get_campaign_decision, in minor units. Pass through unchanged."),
14413
+ spend_minor_units: z4.number().nonnegative().describe("Spend the decision was taken against, in minor units of `currency`."),
14414
+ currency: z4.string().length(3).describe("ISO 4217 code for spend and revenue on this decision, e.g. USD. Three letters."),
14415
+ band_width_pct: z4.number().nonnegative().describe("Width of the profit band as a percentage, from get_campaign_decision. It is how the maturity check later judges whether the range was tight enough to act on."),
14416
+ threshold: z4.number().describe("The profit threshold the call was made against, in minor units."),
14417
+ model_version: z4.string().describe("Model version string from get_campaign_decision. Recorded so a later evaluation knows which model produced the range."),
14418
+ cost_model_version: z4.number().int().positive().nullable().describe("Cost model version from get_campaign_decision, or null when no cost model applied."),
14419
+ revenue_per_install_minor_units: z4.number().nonnegative().nullable().describe("Revenue per install used in the estimate, in minor units, or null when revenue was unavailable."),
14394
14420
  revenue_per_install_quality: z4.enum([
14395
14421
  "campaign_verified_cohort",
14396
14422
  "app_verified_cohort_fallback",
14397
14423
  "unavailable"
14398
- ]),
14424
+ ]).describe("How that revenue figure was derived: from this campaign's own verified cohort, from the app-wide fallback, or not at all. Pass through from get_campaign_decision."),
14399
14425
  idempotency_key: idempotencyKeySchema
14400
14426
  }
14401
14427
  }, async ({
@@ -14439,11 +14465,13 @@ function createServer(config, fetchImpl) {
14439
14465
  }
14440
14466
  })));
14441
14467
  server.registerTool("get_attribution_rollup", {
14468
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14442
14469
  title: "Get attribution rollup",
14443
14470
  description: "Return the account-level attribution overview: installs accounted-for by evidence class (verified / device-matched / modeled / organic), MER, revenue and data freshness.",
14444
14471
  inputSchema: { app_id: appIdSchema, period: periodSchema }
14445
14472
  }, async ({ app_id, period }) => toToolResult(await call("/api/v1/reports/overview", { query: { appId: app_id, period } })));
14446
14473
  server.registerTool("create_campaign_link", {
14474
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14447
14475
  title: "Create a campaign tracking link",
14448
14476
  description: "Create a web-to-app tracking link for an ad campaign. Requires a write-scoped API key. Returns the link id and url.",
14449
14477
  inputSchema: {
@@ -14451,10 +14479,11 @@ function createServer(config, fetchImpl) {
14451
14479
  network: z4.enum(["meta", "google", "tiktok"]).describe("The ad network delivering the campaign."),
14452
14480
  destination_url: z4.string().url().describe("The App Store URL to send users to."),
14453
14481
  campaign_name: z4.string().max(120).optional().describe("Optional human-readable campaign name."),
14454
- campaign_id: z4.string().optional().describe("Optional external campaign id."),
14482
+ campaign_id: z4.string().optional().describe("Optional external campaign id. This is the AD NETWORK's own campaign id, not an AttrKit uuid; it is the opposite of import_spend.campaign_id, which requires the AttrKit one."),
14483
+ serve_landing: z4.boolean().optional().describe("Serve the interstitial landing bridge on this link. Defaults to true at the API. The bridge is what captures browser-side context for device matching, so turning it off narrows what can be matched."),
14455
14484
  idempotency_key: idempotencyKeySchema
14456
14485
  }
14457
- }, async ({ app_id, network, destination_url, campaign_name, campaign_id, idempotency_key }) => toToolResult(await call("/api/v1/links", {
14486
+ }, async ({ app_id, network, destination_url, campaign_name, campaign_id, serve_landing, idempotency_key }) => toToolResult(await call("/api/v1/links", {
14458
14487
  method: "POST",
14459
14488
  idempotencyKey: idempotency_key,
14460
14489
  body: {
@@ -14462,10 +14491,12 @@ function createServer(config, fetchImpl) {
14462
14491
  network,
14463
14492
  destinationUrl: destination_url,
14464
14493
  ...campaign_name ? { campaignName: campaign_name } : {},
14465
- ...campaign_id ? { campaignId: campaign_id } : {}
14494
+ ...campaign_id ? { campaignId: campaign_id } : {},
14495
+ ...serve_landing === undefined ? {} : { serveLanding: serve_landing }
14466
14496
  }
14467
14497
  })));
14468
14498
  server.registerTool("update_link_status", {
14499
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14469
14500
  title: "Enable or disable a campaign link",
14470
14501
  description: "Change a tracking link between active and disabled.",
14471
14502
  inputSchema: {
@@ -14475,6 +14506,7 @@ function createServer(config, fetchImpl) {
14475
14506
  }
14476
14507
  }, async ({ link_id, status, idempotency_key }) => toToolResult(await call(`/api/v1/links/${encodeURIComponent(link_id)}`, { method: "PATCH", idempotencyKey: idempotency_key, body: { status } })));
14477
14508
  server.registerTool("get_spend", {
14509
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14478
14510
  title: "Get spend and cost models",
14479
14511
  description: "Return imported ad spend, cost models, completeness and spend freshness.",
14480
14512
  inputSchema: { app_id: appIdSchema }
@@ -14482,6 +14514,7 @@ function createServer(config, fetchImpl) {
14482
14514
  query: { appId: app_id }
14483
14515
  })));
14484
14516
  server.registerTool("import_spend", {
14517
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14485
14518
  title: "Import ad spend",
14486
14519
  description: "Import campaign or creative-level spend rows.",
14487
14520
  inputSchema: {
@@ -14530,6 +14563,7 @@ function createServer(config, fetchImpl) {
14530
14563
  }
14531
14564
  })));
14532
14565
  server.registerTool("set_cost_model", {
14566
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14533
14567
  title: "Set an app cost model",
14534
14568
  description: "Version commission, payment fees and variable COGS used in profit decisions.",
14535
14569
  inputSchema: {
@@ -14565,6 +14599,7 @@ function createServer(config, fetchImpl) {
14565
14599
  }
14566
14600
  })));
14567
14601
  server.registerTool("pull_network_spend", {
14602
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14568
14603
  title: "Pull spend from an ad network",
14569
14604
  description: "Fetch and import creative-level spend from a configured Meta, Google or TikTok connection.",
14570
14605
  inputSchema: {
@@ -14588,18 +14623,20 @@ function createServer(config, fetchImpl) {
14588
14623
  }
14589
14624
  })));
14590
14625
  server.registerTool("list_integrations", {
14626
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14591
14627
  title: "List ad-network integrations",
14592
14628
  description: "List redacted ad-network connections and their readiness.",
14593
14629
  inputSchema: {}
14594
14630
  }, async () => toToolResult(await call("/api/v1/integrations")));
14595
14631
  server.registerTool("create_integration", {
14632
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14596
14633
  title: "Create an ad-network integration",
14597
14634
  description: "Store encrypted network credentials and configure event ownership.",
14598
14635
  inputSchema: {
14599
14636
  network: z4.enum(["meta", "google", "tiktok"]),
14600
14637
  account_ref: z4.string().min(1),
14601
14638
  credential: z4.string().min(1),
14602
- owners: ownersSchema,
14639
+ owners: ownersSchema.describe("Which destination owns each event family. Enabling a destination requires ALL eight families to be present, or the API rejects the call."),
14603
14640
  enable: z4.boolean().optional(),
14604
14641
  idempotency_key: idempotencyKeySchema
14605
14642
  }
@@ -14609,6 +14646,7 @@ function createServer(config, fetchImpl) {
14609
14646
  body: { network, account_ref, credential, owners, enable: enable ?? false }
14610
14647
  })));
14611
14648
  server.registerTool("update_integration", {
14649
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14612
14650
  title: "Update an ad-network integration",
14613
14651
  description: "Rotate credentials or enable/disable an integration.",
14614
14652
  inputSchema: {
@@ -14628,21 +14666,24 @@ function createServer(config, fetchImpl) {
14628
14666
  }
14629
14667
  })));
14630
14668
  server.registerTool("delete_integration", {
14669
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
14631
14670
  title: "Delete an ad-network integration",
14632
14671
  description: "Delete a disabled integration. Enabled integrations must be disabled first.",
14633
14672
  inputSchema: { connection_id: z4.string() }
14634
14673
  }, async ({ connection_id }) => toToolResult(await call(`/api/v1/integrations/${encodeURIComponent(connection_id)}`, { method: "DELETE" })));
14635
14674
  server.registerTool("get_revenuecat", {
14675
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14636
14676
  title: "Get RevenueCat configuration",
14637
14677
  description: "Return redacted RevenueCat status and the AttrKit webhook URL.",
14638
14678
  inputSchema: { app_id: requiredAppIdSchema }
14639
14679
  }, async ({ app_id }) => toToolResult(await call(`/api/v1/apps/${encodeURIComponent(app_id)}/revenuecat`)));
14640
14680
  server.registerTool("configure_revenuecat", {
14681
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14641
14682
  title: "Configure RevenueCat",
14642
14683
  description: "Store the RevenueCat provider app id and encrypted authorization secret for an app.",
14643
14684
  inputSchema: {
14644
14685
  app_id: requiredAppIdSchema,
14645
- provider_app_id: revenueCatProviderAppIdSchema,
14686
+ provider_app_id: revenueCatProviderAppIdSchema.describe("RevenueCat's PROJECT app id from their dashboard app settings, not the SDK key."),
14646
14687
  authorization_secret: z4.string().min(16).max(1024),
14647
14688
  idempotency_key: idempotencyKeySchema
14648
14689
  }
@@ -14652,11 +14693,13 @@ function createServer(config, fetchImpl) {
14652
14693
  body: { provider_app_id, authorization_secret }
14653
14694
  })));
14654
14695
  server.registerTool("list_webhooks", {
14696
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14655
14697
  title: "List outbound webhooks",
14656
14698
  description: "List redacted outbound webhook subscriptions.",
14657
14699
  inputSchema: {}
14658
14700
  }, async () => toToolResult(await call("/api/v1/webhooks")));
14659
14701
  server.registerTool("create_webhook", {
14702
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
14660
14703
  title: "Create an outbound webhook",
14661
14704
  description: "Create an SSRF-checked webhook subscription. Returns the signing secret once.",
14662
14705
  inputSchema: {
@@ -14677,11 +14720,13 @@ function createServer(config, fetchImpl) {
14677
14720
  }
14678
14721
  })));
14679
14722
  server.registerTool("delete_webhook", {
14723
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
14680
14724
  title: "Delete an outbound webhook",
14681
14725
  description: "Delete a webhook subscription.",
14682
14726
  inputSchema: { subscription_id: z4.string().uuid() }
14683
14727
  }, async ({ subscription_id }) => toToolResult(await call(`/api/v1/webhooks/${encodeURIComponent(subscription_id)}`, { method: "DELETE" })));
14684
14728
  server.registerTool("export_events", {
14729
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14685
14730
  title: "Export events and installs",
14686
14731
  description: "Export one app's events and installs as CSV for an inclusive date range.",
14687
14732
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attrkit/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "AttrKit's stdio MCP server for its public Management API",
5
5
  "keywords": [
6
6
  "attrkit",