dtc-mcp 1.0.4 → 1.0.5

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/data/docs.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "v2026-05-25",
3
- "generatedAt": "2026-05-25T05:28:37.305Z",
3
+ "generatedAt": "2026-05-25T22:02:22.894Z",
4
4
  "chunks": [
5
5
  {
6
6
  "id": "guide.output-discipline",
@@ -95,7 +95,7 @@
95
95
  "platform": "guide",
96
96
  "category": "guide",
97
97
  "summary": "The sandbox keeps one context alive per MCP connection. Assign to globalThis to share data across execute_code calls. Beats Stainless's stateless Cloudflare-Workers model for iterative DTC analyses.",
98
- "content": "## Stateful sandbox sessions\n\nThe sandbox keeps a **single context alive for the lifetime of your MCP connection**. Variables you assign to `globalThis` in one `execute_code` call are visible in every subsequent call within the same conversation. This is intentional and is one of the architectural differences from Stainless (which runs each call in a fresh Cloudflare Worker isolate — stateless).\n\nFor multi-step DTC analyses (a typical workflow: fetch campaigns → drill into a specific one → cross-reference Shopify orders), this means:\n- The first call fetches and caches expensive data into `globalThis`.\n- Subsequent calls reference those values directly without re-fetching.\n- The host's reporting cache STILL handles repeat API calls within the same session — statefulness compounds with it.\n\n### Sharing data: use globalThis\n\nBecause user code runs as a fresh script per call, top-level `const` / `let` declarations are **scoped to that call only** — they're NOT visible later. Use `globalThis` (or `globalThis.x = ...` shorthand) for anything you want to carry forward.\n\n```js\n// Call 1\nconst metricId = await klaviyo.getConversionMetricId();\nglobalThis.metricId = metricId; // persisted\nconst topCampaigns = topN(\n (await klaviyo.reporting.campaignValues({\n data: { type: 'campaign-values-report', attributes: {\n timeframe: { key: 'last_30_days' },\n conversion_metric_id: metricId,\n statistics: ['conversion_value', 'recipients']\n }}\n })).data.attributes.results,\n 5,\n (r) => r.statistics.conversion_value\n);\nglobalThis.topCampaigns = topCampaigns; // persisted\nreturn pick(topCampaigns, { groupings: { campaign_id: true }, statistics: { conversion_value: true } });\n```\n\n```js\n// Call 2 — no re-fetch needed\nconst topIds = globalThis.topCampaigns.map(c => c.groupings.campaign_id);\nconst details = await Promise.all(topIds.map(id =>\n klaviyo.campaigns.get(id, { 'fields[campaign]': 'name,send_time' })\n));\nreturn details.map(d => ({ id: d.data.id, name: d.data.attributes.name }));\n```\n\n### Session reset (TTL + memory)\n\nThe context is recreated when any of the following happens:\n- The MCP connection closes (Claude Desktop is quit or the extension is disabled)\n- 30 minutes of idle time pass with no `execute_code` call (configurable via env)\n- Memory usage exceeds the per-isolate cap (256 MB)\n- The user manually reloads the extension\n\nWhen the context is recreated, the next call's response includes `sessionReset: true` and a `sessionResetNote`. If you see that, any globals you set previously are gone — re-declare what you need.\n\n```js\n// You can detect a reset by checking globalThis.\nif (typeof globalThis.cachedMetricId === 'undefined') {\n globalThis.cachedMetricId = await klaviyo.getConversionMetricId();\n}\nconst metricId = globalThis.cachedMetricId;\n```\n\n### Why this beats Stainless's stateless model\n\nStainless's Cloudflare-Workers sandbox runs each `execute` in a fresh isolate. For iterative analyses they require re-fetching at every step, which inflates token cost AND duration. Our self-hosted sidecar trivially supports state because it owns the isolate lifecycle.",
98
+ "content": "## Stateful sandbox sessions\n\nThe sandbox keeps a **single context alive for the lifetime of your MCP connection**. Variables you assign to `globalThis` in one `execute_code` call are visible in every subsequent call within the same conversation. This is intentional and is one of the architectural differences from Stainless (which runs each call in a fresh Cloudflare Worker isolate — stateless).\n\nFor multi-step DTC analyses (a typical workflow: fetch campaigns → drill into a specific one → cross-reference Shopify orders), this means:\n- The first call fetches and caches expensive data into `globalThis`.\n- Subsequent calls reference those values directly without re-fetching.\n- The host's reporting cache STILL handles repeat API calls within the same session — statefulness compounds with it.\n\n### Discovering what's stashed: use globals()\n\nAt the start of any follow-up call, run `globals()` to see what data is already stashed from prior calls. The helper returns `{ name: summary }` for every user-added global (e.g. `{ topCampaigns: 'Array(5)', metricId: 'string(8 chars)' }`). This is the cheap way to avoid re-fetching: check what's there before fetching again.\n\n```js\n// Start of a follow-up call\nconst stashed = globals();\nif (stashed.topCampaigns) {\n // reuse globalThis.topCampaigns — skip the re-fetch\n return globalThis.topCampaigns;\n}\n```\n\n### Sharing data: use globalThis\n\nBecause user code runs as a fresh script per call, top-level `const` / `let` declarations are **scoped to that call only** — they're NOT visible later. Use `globalThis` (or `globalThis.x = ...` shorthand) for anything you want to carry forward.\n\n```js\n// Call 1\nconst metricId = await klaviyo.getConversionMetricId();\nglobalThis.metricId = metricId; // persisted\nconst topCampaigns = topN(\n (await klaviyo.reporting.campaignValues({\n data: { type: 'campaign-values-report', attributes: {\n timeframe: { key: 'last_30_days' },\n conversion_metric_id: metricId,\n statistics: ['conversion_value', 'recipients']\n }}\n })).data.attributes.results,\n 5,\n (r) => r.statistics.conversion_value\n);\nglobalThis.topCampaigns = topCampaigns; // persisted\nreturn pick(topCampaigns, { groupings: { campaign_id: true }, statistics: { conversion_value: true } });\n```\n\n```js\n// Call 2 — no re-fetch needed\nconst topIds = globalThis.topCampaigns.map(c => c.groupings.campaign_id);\nconst details = await Promise.all(topIds.map(id =>\n klaviyo.campaigns.get(id, { 'fields[campaign]': 'name,send_time' })\n));\nreturn details.map(d => ({ id: d.data.id, name: d.data.attributes.name }));\n```\n\n### Session reset (TTL + memory)\n\nThe context is recreated when any of the following happens:\n- The MCP connection closes (Claude Desktop is quit or the extension is disabled)\n- 30 minutes of idle time pass with no `execute_code` call (configurable via env)\n- Memory usage exceeds the per-isolate cap (256 MB)\n- The user manually reloads the extension\n\nWhen the context is recreated, the next call's response includes `sessionReset: true` and a `sessionResetNote`. If you see that, any globals you set previously are gone — re-declare what you need.\n\n```js\n// You can detect a reset by checking globalThis.\nif (typeof globalThis.cachedMetricId === 'undefined') {\n globalThis.cachedMetricId = await klaviyo.getConversionMetricId();\n}\nconst metricId = globalThis.cachedMetricId;\n```\n\n### Why this beats Stainless's stateless model\n\nStainless's Cloudflare-Workers sandbox runs each `execute` in a fresh isolate. For iterative analyses they require re-fetching at every step, which inflates token cost AND duration. Our self-hosted sidecar trivially supports state because it owns the isolate lifecycle.",
99
99
  "tags": [
100
100
  "stateful",
101
101
  "sessions",
@@ -94,5 +94,59 @@ globalThis.summarize = function summarize(arr, opts) {
94
94
  }
95
95
  return result;
96
96
  };
97
+
98
+ /**
99
+ * globals() — introspect what's currently stashed on globalThis.
100
+ *
101
+ * Returns { name: summary } for every user-added global, filtering out the
102
+ * sandbox's built-in helpers and standard JS globals. Use at the start of a
103
+ * follow-up turn to see what data is already available from prior calls and
104
+ * avoid re-fetching anything that's already there.
105
+ *
106
+ * // Call 1
107
+ * globalThis.flowReport = await klaviyo.reporting.flowValues({...});
108
+ *
109
+ * // Call 2 (next turn) — check what's stashed before re-fetching
110
+ * const stashed = globals();
111
+ * // → { flowReport: 'Object(2 keys)' }
112
+ *
113
+ * Implementation: captures the baseline of Object.keys(globalThis) at module
114
+ * load time via an IIFE closure. Anything added after bootstrap is "user
115
+ * state" and shows up in the listing.
116
+ */
117
+ (function () {
118
+ const __baseline = new Set(Object.keys(globalThis));
119
+ globalThis.globals = function globals() {
120
+ const out = {};
121
+ for (const k of Object.keys(globalThis)) {
122
+ if (__baseline.has(k)) continue;
123
+ if (k === 'globals') continue;
124
+ if (k.startsWith('__')) continue;
125
+ let summary;
126
+ try {
127
+ const v = globalThis[k];
128
+ if (v === null || v === undefined) {
129
+ summary = String(v);
130
+ } else if (Array.isArray(v)) {
131
+ summary = 'Array(' + v.length + ')';
132
+ } else if (typeof v === 'object') {
133
+ summary = 'Object(' + Object.keys(v).length + ' keys)';
134
+ } else if (typeof v === 'string') {
135
+ summary = v.length > 40
136
+ ? 'string(' + v.length + ' chars)'
137
+ : JSON.stringify(v);
138
+ } else if (typeof v === 'function') {
139
+ summary = 'function';
140
+ } else {
141
+ summary = typeof v + ': ' + String(v);
142
+ }
143
+ } catch (_e) {
144
+ summary = '<unreadable>';
145
+ }
146
+ out[k] = summary;
147
+ }
148
+ return out;
149
+ };
150
+ })();
97
151
  `.trim();
98
152
  //# sourceMappingURL=sandbox-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-helpers.js","sourceRoot":"","sources":["../../src/sandbox/sandbox-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFrC,CAAC,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"sandbox-helpers.js","sourceRoot":"","sources":["../../src/sandbox/sandbox-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyIrC,CAAC,IAAI,EAAE,CAAC"}
@@ -3,7 +3,7 @@ import { runSandbox } from "../sandbox/runner.js";
3
3
  import { resolveTimeout } from "../sandbox/timeout.js";
4
4
  import { log } from "../config.js";
5
5
  const codeShape = {
6
- code: z.string().describe("TypeScript-like JavaScript to execute. Wrap top-level await calls naturally — the code runs in an async context. Return a value via `return ...` to receive it as the tool result. Globals available: `klaviyo`, `shopify`, `console`. No `fetch`/`process`/`require`/`import`. Add `// @timeout 2m` (max 5m) at the top to extend the default 30s wall-clock limit. Discover SDK methods via the `search_docs` tool."),
6
+ code: z.string().describe("TypeScript-like JavaScript to execute. Wrap top-level await calls naturally — the code runs in an async context. Return a value via `return ...` to receive it as the tool result. Globals available: `klaviyo`, `shopify`, `console`, plus helpers `pick`, `topN`, `summarize`, `globals`. No `fetch`/`process`/`require`/`import`. Add `// @timeout 2m` (max 5m) at the top to extend the default 30s wall-clock limit. Discover SDK methods via the `search_docs` tool."),
7
7
  };
8
8
  const description = `
9
9
  Execute JavaScript against the typed Klaviyo + Shopify SDKs in a stateful V8 sandbox.
@@ -12,11 +12,18 @@ The host applies rate limits, auth, and caching transparently. The sandbox keeps
12
12
  context alive per MCP connection — variables you assign to globalThis persist across
13
13
  calls, so iterative analyses don't re-fetch.
14
14
 
15
+ STRONGLY RECOMMENDED for multi-turn investigations: stash any expensive fetch
16
+ (reporting payloads, paginated lists, computed aggregates) on globalThis so
17
+ follow-up turns reference the stashed data instead of re-fetching it. Re-running
18
+ a 5,000-row report costs ~30k tokens; reading globalThis.report costs near zero.
19
+ Call \`globals()\` at the start of any follow-up call to see what's already stashed.
20
+
15
21
  Available globals:
16
22
  - klaviyo: { get, post, paginate, campaigns, flows, lists, segments, profiles, events, metrics, reporting }
17
23
  - shopify: { gql, ql, timezone } — Shopify Admin GraphQL + ShopifyQL
18
24
  - console: { log, error, warn, info } — captured and returned as stdout
19
25
  - pick(value, schema) / topN(arr, n, by) / summarize(arr, opts) — output-discipline helpers
26
+ - globals() — returns { name: summary } of everything currently stashed on globalThis
20
27
  - globalThis.* — assignments persist across calls within this MCP session
21
28
 
22
29
  Discovery: use read_doc({}) at session start to list every available SDK path, then
@@ -1 +1 @@
1
- {"version":3,"file":"execute_code.js","sourceRoot":"","sources":["../../src/tools/execute_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CACvB,uZAAuZ,CACxZ;CACF,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCnB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,IAAI,CACT,cAAc,EACd,WAAW,EACX,SAAS,EACT;QACE,KAAK,EAAE,0BAA0B;QACjC,YAAY,EAAE,KAAK;QACnB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,KAAK;QACrB,aAAa,EAAE,IAAI;KACpB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACvC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAEjE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAErD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CACzB;YACE,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACpE,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,CAAC,MAAM,CAAC,YAAY;gBACrB,CAAC,CAAC;oBACE,YAAY,EAAE,IAAI;oBAClB,gBAAgB,EACd,8SAA8S;iBACjT;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,EACD,IAAI,EACJ,CAAC,CACF,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE;SACpB,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"execute_code.js","sourceRoot":"","sources":["../../src/tools/execute_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CACvB,4cAA4c,CAC7c;CACF,CAAC;AAEF,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CnB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,IAAI,CACT,cAAc,EACd,WAAW,EACX,SAAS,EACT;QACE,KAAK,EAAE,0BAA0B;QACjC,YAAY,EAAE,KAAK;QACnB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,KAAK;QACrB,aAAa,EAAE,IAAI;KACpB,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACvC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAEjE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAErD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CACzB;YACE,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YACpE,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,GAAG,CAAC,MAAM,CAAC,YAAY;gBACrB,CAAC,CAAC;oBACE,YAAY,EAAE,IAAI;oBAClB,gBAAgB,EACd,8SAA8S;iBACjT;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,EACD,IAAI,EACJ,CAAC,CACF,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE;SACpB,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dtc-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Code-execution MCP server for Klaviyo + Shopify analytics. The LLM writes TypeScript against typed SDKs in a stateful V8 sandbox — three composable tools instead of dozens.",
5
5
  "type": "module",
6
6
  "bin": {