@attrkit/mcp 0.4.2 → 0.4.4

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +31 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -33,7 +33,7 @@ Install Node.js 18 or newer. The `npx -y @attrkit/mcp` command downloads and
33
33
  starts the server automatically, so no global install is required.
34
34
 
35
35
  Create the first workspace management key under
36
- [Dashboard → Settings → API keys](https://attrikit.io/en/dashboard/settings/api-keys).
36
+ [Dashboard → Settings → API keys & MCP](https://attrikit.io/en/dashboard/settings/api-keys).
37
37
  This one-time bootstrap requires the dashboard.
38
38
 
39
39
  After that first key exists, a workspace-scoped read/write key can manage every
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,13 @@ 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 redact = (text) => text.replace(/\b(?:mk|pk|sk|rk)_[A-Za-z0-9_-]{8,}/g, "[redacted-key]").replace(/\b[Bb]earer\s+[A-Za-z0-9._-]{8,}/g, "Bearer [redacted]").replace(/\bAttriKit-(?:Management|Publishable)\s+\S+/g, "AttriKit-$1 [redacted]").replace(/([?&](?:token|secret|key|password|code|signature)=)[^&\s]+/gi, "$1[redacted]");
14080
+ const excerpt = !envelope?.error?.message && result.rawBody ? ` — body: ${redact(result.rawBody.replace(/\s+/g, " ").trim()).slice(0, 300)}` : "";
14081
+ const hint = result.status === 401 ? " Check ATTRKIT_API_KEY: this is the same error for a missing, wrong, revoked or expired key." : "";
14074
14082
  return {
14075
14083
  content: [{
14076
14084
  type: "text",
14077
- text: `AttrKit API error (${result.status || "network"}): ${code}${message}${retryAfter}`
14085
+ text: `AttrKit API error (${result.status || "network"}): ${code}${message}${retryAfter}${excerpt}${hint}`
14078
14086
  }],
14079
14087
  isError: true
14080
14088
  };
@@ -14083,7 +14091,8 @@ function toToolResult(result) {
14083
14091
  const isEmptySuccess = result.status === 204 && (result.rawBody === undefined || result.rawBody === "");
14084
14092
  const isJsonEnvelope = result.json !== null && typeof result.json === "object" && !Array.isArray(result.json) && Object.hasOwn(result.json, "data");
14085
14093
  const contentType = (result.contentType ?? "").toLowerCase();
14086
- const isExpectedText = expectedResponse === "text" && typeof result.json === "string" && (contentType.includes("text/csv") || contentType.includes("application/csv"));
14094
+ const looksLikeMarkup = typeof result.json === "string" && /^\s*<(?:!doctype|html|head|body)\b/i.test(result.json);
14095
+ const isExpectedText = expectedResponse === "text" && typeof result.json === "string" && !contentType.includes("text/html") && !looksLikeMarkup;
14087
14096
  if (!isEmptySuccess && !isJsonEnvelope && !isExpectedText) {
14088
14097
  const reportedContentType = result.contentType ?? "unknown content type";
14089
14098
  const received = result.json === null ? "an empty body" : Array.isArray(result.json) ? "a JSON array" : typeof result.json === "object" ? "a JSON object without a data field" : `a ${typeof result.json} body`;
@@ -14103,16 +14112,16 @@ ${body}`
14103
14112
  structuredContent: structured
14104
14113
  };
14105
14114
  }
14106
- var SERVER_VERSION = "0.4.2";
14115
+ var SERVER_VERSION = "0.4.4";
14107
14116
  var periodSchema = z4.enum(["7d", "30d", "90d", "all"]).describe("Reporting window. Defaults to 30d.").optional();
14108
14117
  var appIdSchema = z4.string().describe("Optional app id to scope the result to one app.").optional();
14109
14118
  var requiredAppIdSchema = z4.string().describe("The AttrKit app id.");
14110
14119
  var idempotencyKeySchema = z4.string().max(255).describe("Stable retry key for this exact mutation.").optional();
14111
14120
  var ownersSchema = z4.record(z4.string(), z4.enum(["attrkit", "revenuecat", "customer"]));
14112
14121
  var spendRowSchema = z4.object({
14113
- campaign_id: z4.string().optional().describe("Required AttrKit campaign id. Preferred snake_case spelling."),
14122
+ 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
14123
  campaignId: z4.string().optional().describe("Accepted camelCase alias for campaign_id."),
14115
- adset_id: z4.string().optional().nullable(),
14124
+ adset_id: z4.string().optional().nullable().describe("Ad set id. Must be sent together with ad_id; either one alone is rejected."),
14116
14125
  adsetId: z4.string().optional().nullable(),
14117
14126
  ad_id: z4.string().optional().nullable(),
14118
14127
  adId: z4.string().optional().nullable(),
@@ -14397,24 +14406,24 @@ function createServer(config, fetchImpl) {
14397
14406
  description: "Freeze a scale/cut/hold decision and its exact model inputs for later maturity evaluation.",
14398
14407
  inputSchema: {
14399
14408
  app_id: requiredAppIdSchema,
14400
- campaign_id: z4.string(),
14401
- call: z4.enum(["scale", "cut", "hold"]),
14402
- actor: z4.enum(["dashboard", "auto_suggested"]).optional(),
14403
- p05: z4.number(),
14404
- p50: z4.number(),
14405
- p95: z4.number(),
14406
- spend_minor_units: z4.number().nonnegative(),
14407
- currency: z4.string().length(3),
14408
- band_width_pct: z4.number().nonnegative(),
14409
- threshold: z4.number(),
14410
- model_version: z4.string(),
14411
- cost_model_version: z4.number().int().positive().nullable(),
14412
- revenue_per_install_minor_units: z4.number().nonnegative().nullable(),
14409
+ 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."),
14410
+ call: z4.enum(["scale", "cut", "hold"]).describe("The decision being frozen. Report get_campaign_decision's own call verbatim rather than forming your own."),
14411
+ actor: z4.enum(["dashboard", "auto_suggested"]).optional().describe("Who made the call. Use auto_suggested when an agent proposed it without a human confirming."),
14412
+ p05: z4.number().describe("Low end of the profit range from get_campaign_decision, in minor units. Pass through unchanged."),
14413
+ p50: z4.number().describe("Central profit estimate from get_campaign_decision, in minor units. Pass through unchanged."),
14414
+ p95: z4.number().describe("High end of the profit range from get_campaign_decision, in minor units. Pass through unchanged."),
14415
+ spend_minor_units: z4.number().nonnegative().describe("Spend the decision was taken against, in minor units of `currency`."),
14416
+ currency: z4.string().length(3).describe("ISO 4217 code for spend and revenue on this decision, e.g. USD. Three letters."),
14417
+ 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."),
14418
+ threshold: z4.number().describe("The profit threshold the call was made against, in minor units."),
14419
+ model_version: z4.string().describe("Model version string from get_campaign_decision. Recorded so a later evaluation knows which model produced the range."),
14420
+ cost_model_version: z4.number().int().positive().nullable().describe("Cost model version from get_campaign_decision, or null when no cost model applied."),
14421
+ 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."),
14413
14422
  revenue_per_install_quality: z4.enum([
14414
14423
  "campaign_verified_cohort",
14415
14424
  "app_verified_cohort_fallback",
14416
14425
  "unavailable"
14417
- ]),
14426
+ ]).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."),
14418
14427
  idempotency_key: idempotencyKeySchema
14419
14428
  }
14420
14429
  }, async ({
@@ -14629,7 +14638,7 @@ function createServer(config, fetchImpl) {
14629
14638
  network: z4.enum(["meta", "google", "tiktok"]),
14630
14639
  account_ref: z4.string().min(1),
14631
14640
  credential: z4.string().min(1),
14632
- owners: ownersSchema,
14641
+ 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."),
14633
14642
  enable: z4.boolean().optional(),
14634
14643
  idempotency_key: idempotencyKeySchema
14635
14644
  }
@@ -14676,7 +14685,7 @@ function createServer(config, fetchImpl) {
14676
14685
  description: "Store the RevenueCat provider app id and encrypted authorization secret for an app.",
14677
14686
  inputSchema: {
14678
14687
  app_id: requiredAppIdSchema,
14679
- provider_app_id: revenueCatProviderAppIdSchema,
14688
+ provider_app_id: revenueCatProviderAppIdSchema.describe("RevenueCat's PROJECT app id from their dashboard app settings, not the SDK key."),
14680
14689
  authorization_secret: z4.string().min(16).max(1024),
14681
14690
  idempotency_key: idempotencyKeySchema
14682
14691
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attrkit/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "AttrKit's stdio MCP server for its public Management API",
5
5
  "keywords": [
6
6
  "attrkit",