dtc-mcp 0.2.0 → 1.0.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 (89) hide show
  1. package/README.md +169 -402
  2. package/data/docs.json +4338 -0
  3. package/dist/docs/loader.d.ts +40 -0
  4. package/dist/docs/loader.js +110 -0
  5. package/dist/docs/loader.js.map +1 -0
  6. package/dist/docs/search.d.ts +47 -0
  7. package/dist/docs/search.js +101 -0
  8. package/dist/docs/search.js.map +1 -0
  9. package/dist/index.js +3 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/sandbox/bridge.d.ts +2 -0
  12. package/dist/sandbox/bridge.js +101 -0
  13. package/dist/sandbox/bridge.js.map +1 -0
  14. package/dist/sandbox/node-discovery.d.ts +7 -0
  15. package/dist/sandbox/node-discovery.js +228 -0
  16. package/dist/sandbox/node-discovery.js.map +1 -0
  17. package/dist/sandbox/protocol.d.ts +76 -0
  18. package/dist/sandbox/protocol.js +20 -0
  19. package/dist/sandbox/protocol.js.map +1 -0
  20. package/dist/sandbox/proxy-template.d.ts +19 -0
  21. package/dist/sandbox/proxy-template.js +83 -0
  22. package/dist/sandbox/proxy-template.js.map +1 -0
  23. package/dist/sandbox/runner.d.ts +20 -0
  24. package/dist/sandbox/runner.js +99 -0
  25. package/dist/sandbox/runner.js.map +1 -0
  26. package/dist/sandbox/sandbox-helpers.d.ts +14 -0
  27. package/dist/sandbox/sandbox-helpers.js +98 -0
  28. package/dist/sandbox/sandbox-helpers.js.map +1 -0
  29. package/dist/sandbox/sidecar/index.d.ts +16 -0
  30. package/dist/sandbox/sidecar/index.js +346 -0
  31. package/dist/sandbox/sidecar/index.js.map +1 -0
  32. package/dist/sandbox/sidecar-runner.d.ts +32 -0
  33. package/dist/sandbox/sidecar-runner.js +325 -0
  34. package/dist/sandbox/sidecar-runner.js.map +1 -0
  35. package/dist/sandbox/timeout.d.ts +16 -0
  36. package/dist/sandbox/timeout.js +38 -0
  37. package/dist/sandbox/timeout.js.map +1 -0
  38. package/dist/sandbox/vm-runner.d.ts +35 -0
  39. package/dist/sandbox/vm-runner.js +182 -0
  40. package/dist/sandbox/vm-runner.js.map +1 -0
  41. package/dist/sdk/klaviyo/host.d.ts +43 -0
  42. package/dist/sdk/klaviyo/host.js +218 -0
  43. package/dist/sdk/klaviyo/host.js.map +1 -0
  44. package/dist/sdk/shopify/host.d.ts +23 -0
  45. package/dist/sdk/shopify/host.js +175 -0
  46. package/dist/sdk/shopify/host.js.map +1 -0
  47. package/dist/server.js +7 -7
  48. package/dist/server.js.map +1 -1
  49. package/dist/shared/errors.d.ts +0 -14
  50. package/dist/shared/errors.js +0 -73
  51. package/dist/shared/errors.js.map +1 -1
  52. package/dist/{platforms/klaviyo/tools.d.ts → tools/execute_code.d.ts} +1 -1
  53. package/dist/tools/execute_code.js +70 -0
  54. package/dist/tools/execute_code.js.map +1 -0
  55. package/dist/{platforms/shopify/tools.d.ts → tools/read_doc.d.ts} +1 -1
  56. package/dist/tools/read_doc.js +70 -0
  57. package/dist/tools/read_doc.js.map +1 -0
  58. package/dist/tools/search_docs.d.ts +2 -0
  59. package/dist/tools/search_docs.js +55 -0
  60. package/dist/tools/search_docs.js.map +1 -0
  61. package/package.json +16 -5
  62. package/dist/cross-platform/correlator.d.ts +0 -10
  63. package/dist/cross-platform/correlator.js +0 -166
  64. package/dist/cross-platform/correlator.js.map +0 -1
  65. package/dist/cross-platform/tools.d.ts +0 -2
  66. package/dist/cross-platform/tools.js +0 -30
  67. package/dist/cross-platform/tools.js.map +0 -1
  68. package/dist/platforms/klaviyo/client.d.ts +0 -91
  69. package/dist/platforms/klaviyo/client.js +0 -389
  70. package/dist/platforms/klaviyo/client.js.map +0 -1
  71. package/dist/platforms/klaviyo/tools.js +0 -363
  72. package/dist/platforms/klaviyo/tools.js.map +0 -1
  73. package/dist/platforms/klaviyo/transforms.d.ts +0 -59
  74. package/dist/platforms/klaviyo/transforms.js +0 -326
  75. package/dist/platforms/klaviyo/transforms.js.map +0 -1
  76. package/dist/platforms/shopify/client.d.ts +0 -51
  77. package/dist/platforms/shopify/client.js +0 -352
  78. package/dist/platforms/shopify/client.js.map +0 -1
  79. package/dist/platforms/shopify/tools.js +0 -368
  80. package/dist/platforms/shopify/tools.js.map +0 -1
  81. package/dist/platforms/shopify/transforms.d.ts +0 -83
  82. package/dist/platforms/shopify/transforms.js +0 -308
  83. package/dist/platforms/shopify/transforms.js.map +0 -1
  84. package/dist/shared/pagination.d.ts +0 -21
  85. package/dist/shared/pagination.js +0 -36
  86. package/dist/shared/pagination.js.map +0 -1
  87. package/dist/shared/types.d.ts +0 -318
  88. package/dist/shared/types.js +0 -3
  89. package/dist/shared/types.js.map +0 -1
@@ -0,0 +1,218 @@
1
+ import { config, log } from "../../config.js";
2
+ import { TTLCache, buildCacheKey } from "../../shared/cache.js";
3
+ import { KlaviyoApiError } from "../../shared/errors.js";
4
+ const BASE_URL = "https://a.klaviyo.com/api";
5
+ const REPORTING_CACHE_TTL = 10 * 60 * 1000;
6
+ const reportingCache = new TTLCache(REPORTING_CACHE_TTL);
7
+ const metricIdCache = new TTLCache(Number.MAX_SAFE_INTEGER);
8
+ // Klaviyo's per-endpoint tiers aren't surfaced uniformly in their docs;
9
+ // the reporting POST endpoints are a strict 1/s + 2/min, while everything
10
+ // else gets the much friendlier 10/s + 150/min.
11
+ class RateLimiter {
12
+ burstPerSecond;
13
+ steadyPerMinute;
14
+ recent = [];
15
+ constructor(burstPerSecond, steadyPerMinute) {
16
+ this.burstPerSecond = burstPerSecond;
17
+ this.steadyPerMinute = steadyPerMinute;
18
+ }
19
+ async acquire() {
20
+ const now = Date.now();
21
+ this.recent = this.recent.filter((t) => now - t < 60_000);
22
+ if (this.recent.length >= this.steadyPerMinute) {
23
+ const wait = 60_000 - (now - this.recent[0]) + jitter();
24
+ await sleep(wait);
25
+ }
26
+ const last1s = this.recent.filter((t) => Date.now() - t < 1_000);
27
+ if (last1s.length >= this.burstPerSecond) {
28
+ const wait = 1_000 - (Date.now() - last1s[0]) + jitter();
29
+ await sleep(wait);
30
+ }
31
+ this.recent.push(Date.now());
32
+ }
33
+ }
34
+ const standardLimiter = new RateLimiter(10, 150);
35
+ const reportingLimiter = new RateLimiter(1, 2);
36
+ function limiterFor(tier) {
37
+ return tier === "reporting" ? reportingLimiter : standardLimiter;
38
+ }
39
+ function headers() {
40
+ return {
41
+ Authorization: `Klaviyo-API-Key ${config.klaviyoApiKey}`,
42
+ revision: config.klaviyoRevision,
43
+ "Content-Type": "application/json",
44
+ Accept: "application/json",
45
+ };
46
+ }
47
+ async function handleResponse(res) {
48
+ if (res.status === 429) {
49
+ const ra = res.headers.get("Retry-After");
50
+ throw new KlaviyoApiError(429, `Rate limited${ra ? `. Retry after ${ra}s` : ""}`);
51
+ }
52
+ if (!res.ok) {
53
+ let msg = `HTTP ${res.status}`;
54
+ try {
55
+ const body = (await res.json());
56
+ if (body.errors?.[0]?.detail)
57
+ msg = body.errors[0].detail;
58
+ }
59
+ catch {
60
+ // ignore
61
+ }
62
+ throw new KlaviyoApiError(res.status, msg);
63
+ }
64
+ return res.json();
65
+ }
66
+ async function withRetry(fn, max = 3) {
67
+ let last;
68
+ for (let i = 0; i <= max; i++) {
69
+ try {
70
+ return await fn();
71
+ }
72
+ catch (e) {
73
+ last = e;
74
+ if (e instanceof KlaviyoApiError && e.status === 429 && i < max) {
75
+ await sleep(Math.pow(2, i) * 1000 + jitter());
76
+ continue;
77
+ }
78
+ throw e;
79
+ }
80
+ }
81
+ throw last;
82
+ }
83
+ /**
84
+ * GET a Klaviyo endpoint. Returns the raw JSON:API response.
85
+ * Bracket params (`page[size]`, `fields[campaign]`) are NOT percent-encoded
86
+ * because Klaviyo's parser doesn't handle the encoded form.
87
+ */
88
+ export async function klaviyoGet(path, options = {}) {
89
+ const tier = options.tier ?? "standard";
90
+ const params = options.params ?? {};
91
+ return withRetry(async () => {
92
+ await limiterFor(tier).acquire();
93
+ let url = `${BASE_URL}/${path.replace(/^\//, "")}`;
94
+ if (Object.keys(params).length) {
95
+ url +=
96
+ "?" +
97
+ Object.entries(params)
98
+ .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
99
+ .join("&");
100
+ }
101
+ log("debug", "Klaviyo GET", { url });
102
+ return handleResponse(await fetch(url, { headers: headers() }));
103
+ });
104
+ }
105
+ /**
106
+ * POST to a Klaviyo endpoint. Used primarily for reporting endpoints.
107
+ * Reporting POSTs are cached with a 10-minute TTL — without this, the LLM
108
+ * will re-fetch the slowest endpoints on every sandbox call.
109
+ */
110
+ export async function klaviyoPost(path, body, options = {}) {
111
+ const tier = options.tier ?? "standard";
112
+ if (tier === "reporting") {
113
+ const key = buildCacheKey(path, body);
114
+ const cached = reportingCache.get(key);
115
+ if (cached !== undefined) {
116
+ log("debug", "Klaviyo POST cache hit", { path });
117
+ return cached;
118
+ }
119
+ }
120
+ const result = await withRetry(async () => {
121
+ await limiterFor(tier).acquire();
122
+ const url = `${BASE_URL}/${path.replace(/^\//, "")}`;
123
+ log("debug", "Klaviyo POST", { url });
124
+ const res = await fetch(url, {
125
+ method: "POST",
126
+ headers: headers(),
127
+ body: JSON.stringify(body),
128
+ });
129
+ return handleResponse(res);
130
+ });
131
+ if (tier === "reporting") {
132
+ reportingCache.set(buildCacheKey(path, body), result);
133
+ }
134
+ return result;
135
+ }
136
+ /**
137
+ * Auto-paginate a GET endpoint. Useful for iterating all metrics, lists, etc.
138
+ * Capped at `maxPages` (default 5 → 500 items at default page size) so a runaway
139
+ * loop in the sandbox can't hammer the API.
140
+ */
141
+ export async function klaviyoPaginate(path, options = {}) {
142
+ const maxPages = options.maxPages ?? 5;
143
+ const items = [];
144
+ let params = { ...(options.params ?? {}) };
145
+ let pages = 0;
146
+ while (pages < maxPages) {
147
+ const raw = (await klaviyoGet(path, { params, tier: options.tier }));
148
+ items.push(...(raw.data ?? []));
149
+ pages++;
150
+ const nextUrl = raw.links?.next;
151
+ if (!nextUrl)
152
+ return { items, truncated: false };
153
+ try {
154
+ const cursor = new URL(nextUrl).searchParams.get("page[cursor]");
155
+ if (!cursor)
156
+ return { items, truncated: false };
157
+ params = { ...(options.params ?? {}), "page[cursor]": cursor };
158
+ }
159
+ catch {
160
+ return { items, truncated: false };
161
+ }
162
+ }
163
+ return { items, truncated: true };
164
+ }
165
+ /**
166
+ * Discover the "Placed Order" conversion metric ID required by reporting endpoints.
167
+ * Cached for server lifetime — never changes per-store.
168
+ *
169
+ * The 2026-01-15 revision doesn't allow filtering metrics by name, so we paginate
170
+ * and match client-side. Exact match first, then fuzzy fallback.
171
+ */
172
+ export async function getConversionMetricId() {
173
+ if (config.klaviyoConversionMetricId) {
174
+ metricIdCache.set("placed_order", config.klaviyoConversionMetricId);
175
+ return config.klaviyoConversionMetricId;
176
+ }
177
+ const cached = metricIdCache.get("placed_order");
178
+ if (cached)
179
+ return cached;
180
+ const { items } = await klaviyoPaginate("metrics", {
181
+ params: { "fields[metric]": "name" },
182
+ });
183
+ const exactNames = ["Placed Order", "Order Placed", "Shopify Placed Order"];
184
+ for (const wanted of exactNames) {
185
+ const m = items.find((item) => String(item.attributes.name ?? "").toLowerCase() ===
186
+ wanted.toLowerCase());
187
+ if (m) {
188
+ metricIdCache.set("placed_order", m.id);
189
+ log("info", "Conversion metric discovered (exact)", {
190
+ name: m.attributes.name,
191
+ id: m.id,
192
+ });
193
+ return m.id;
194
+ }
195
+ }
196
+ const fuzzy = items.find((item) => {
197
+ const n = String(item.attributes.name ?? "").toLowerCase();
198
+ if (n.includes("refund") || n.includes("cancel"))
199
+ return false;
200
+ return (n.includes("placed") && n.includes("order")) || n.includes("purchase");
201
+ });
202
+ if (fuzzy) {
203
+ metricIdCache.set("placed_order", fuzzy.id);
204
+ log("info", "Conversion metric discovered (fuzzy)", {
205
+ name: fuzzy.attributes.name,
206
+ id: fuzzy.id,
207
+ });
208
+ return fuzzy.id;
209
+ }
210
+ throw new KlaviyoApiError(500, 'Could not find "Placed Order" metric. Set KLAVIYO_CONVERSION_METRIC_ID to override.');
211
+ }
212
+ function sleep(ms) {
213
+ return new Promise((r) => setTimeout(r, ms));
214
+ }
215
+ function jitter() {
216
+ return Math.floor(Math.random() * 200);
217
+ }
218
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../../src/sdk/klaviyo/host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface ShopifyGqlOptions {
2
+ variables?: Record<string, unknown>;
3
+ estimatedCost?: number;
4
+ }
5
+ /**
6
+ * Run a GraphQL query against Shopify Admin API. Cost-budget aware: waits
7
+ * until enough capacity restores before sending. Auto-retries once on THROTTLED.
8
+ */
9
+ export declare function shopifyGql<T = unknown>(query: string, options?: ShopifyGqlOptions): Promise<T>;
10
+ export interface ShopifyQLTable {
11
+ columns: Array<{
12
+ name: string;
13
+ dataType: string;
14
+ displayName: string;
15
+ }>;
16
+ rows: Record<string, string>[];
17
+ }
18
+ /**
19
+ * Run a ShopifyQL query. Results cached 5 minutes — repeat queries from the
20
+ * LLM (common when it's iterating an analysis) skip the API round-trip.
21
+ */
22
+ export declare function shopifyQL(ql: string): Promise<ShopifyQLTable>;
23
+ export declare function shopifyTimezone(): Promise<string>;
@@ -0,0 +1,175 @@
1
+ import { config, log } from "../../config.js";
2
+ import { ShopifyApiError } from "../../shared/errors.js";
3
+ import { TTLCache } from "../../shared/cache.js";
4
+ const qlCache = new TTLCache(5 * 60 * 1000);
5
+ // ---- Token Manager: dual-mode auth (Client Credentials / legacy) ----
6
+ //
7
+ // Client Credentials Grant: exchange client_id/secret for a short-lived access
8
+ // token. We cache with a 60s buffer before expiry to avoid mid-flight refresh races.
9
+ // Legacy: a static `shpat_...` token, passed through.
10
+ class TokenManager {
11
+ accessToken = null;
12
+ expiresAt = 0;
13
+ async get() {
14
+ if (config.shopifyAuthMode === "legacy") {
15
+ return config.shopifyAccessToken;
16
+ }
17
+ if (this.accessToken && Date.now() < this.expiresAt - 60_000) {
18
+ return this.accessToken;
19
+ }
20
+ return this.refresh();
21
+ }
22
+ async refresh() {
23
+ log("info", "Acquiring Shopify access token (Client Credentials)");
24
+ const url = `https://${config.shopifyStore}/admin/oauth/access_token`;
25
+ const body = new URLSearchParams({
26
+ grant_type: "client_credentials",
27
+ client_id: config.shopifyClientId,
28
+ client_secret: config.shopifyClientSecret,
29
+ });
30
+ const res = await fetch(url, {
31
+ method: "POST",
32
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
33
+ body: body.toString(),
34
+ });
35
+ if (!res.ok) {
36
+ const text = await res.text();
37
+ throw new ShopifyApiError(`Token request failed (${res.status}): ${text}`);
38
+ }
39
+ const data = (await res.json());
40
+ this.accessToken = data.access_token;
41
+ this.expiresAt = Date.now() + data.expires_in * 1000;
42
+ log("info", "Shopify token acquired", {
43
+ expiresIn: `${Math.round(data.expires_in / 3600)}h`,
44
+ });
45
+ return this.accessToken;
46
+ }
47
+ }
48
+ const tokenManager = new TokenManager();
49
+ // ---- Cost-based throttle (Shopify GraphQL cost budget) ----
50
+ //
51
+ // Shopify returns `extensions.cost.throttleStatus.currentlyAvailable` on every
52
+ // GraphQL response. We mirror their bucket (1000 capacity, 50/s restore) and
53
+ // wait when a query would overdraw it. Misjudging this = hard throttle errors,
54
+ // so we keep the original implementation's tuning.
55
+ class CostTracker {
56
+ available = 1000;
57
+ lastUpdated = Date.now();
58
+ restoreRate = 50;
59
+ async wait(estimatedCost) {
60
+ this.restore();
61
+ if (this.available >= estimatedCost)
62
+ return;
63
+ const deficit = estimatedCost - this.available;
64
+ const wait = (deficit / this.restoreRate) * 1000 + 100;
65
+ log("debug", `Shopify wait ${Math.round(wait)}ms for ${deficit} cost`);
66
+ await new Promise((r) => setTimeout(r, wait));
67
+ this.restore();
68
+ }
69
+ update(ext) {
70
+ const v = ext?.cost?.throttleStatus?.currentlyAvailable;
71
+ if (v !== undefined) {
72
+ this.available = v;
73
+ this.lastUpdated = Date.now();
74
+ }
75
+ }
76
+ restore() {
77
+ const elapsed = (Date.now() - this.lastUpdated) / 1000;
78
+ this.available = Math.min(1000, this.available + elapsed * this.restoreRate);
79
+ this.lastUpdated = Date.now();
80
+ }
81
+ }
82
+ const costTracker = new CostTracker();
83
+ function endpoint() {
84
+ return `https://${config.shopifyStore}/admin/api/${config.shopifyApiVersion}/graphql.json`;
85
+ }
86
+ async function gqlHeaders() {
87
+ return {
88
+ "Content-Type": "application/json",
89
+ "X-Shopify-Access-Token": await tokenManager.get(),
90
+ };
91
+ }
92
+ /**
93
+ * Run a GraphQL query against Shopify Admin API. Cost-budget aware: waits
94
+ * until enough capacity restores before sending. Auto-retries once on THROTTLED.
95
+ */
96
+ export async function shopifyGql(query, options = {}) {
97
+ const cost = options.estimatedCost ?? 10;
98
+ await costTracker.wait(cost);
99
+ let lastErr;
100
+ for (let attempt = 0; attempt <= 3; attempt++) {
101
+ try {
102
+ const res = await fetch(endpoint(), {
103
+ method: "POST",
104
+ headers: await gqlHeaders(),
105
+ body: JSON.stringify({ query, variables: options.variables }),
106
+ });
107
+ if (res.status === 429) {
108
+ const ra = res.headers.get("Retry-After");
109
+ await new Promise((r) => setTimeout(r, ra ? parseInt(ra) * 1000 : 2000));
110
+ continue;
111
+ }
112
+ if (!res.ok) {
113
+ throw new ShopifyApiError(`HTTP ${res.status}: ${res.statusText}`);
114
+ }
115
+ const json = (await res.json());
116
+ costTracker.update(json.extensions);
117
+ if (json.errors?.length) {
118
+ const throttled = json.errors.find((e) => e.extensions?.code === "THROTTLED");
119
+ if (throttled && attempt < 3) {
120
+ await new Promise((r) => setTimeout(r, 2000));
121
+ continue;
122
+ }
123
+ throw new ShopifyApiError(json.errors.map((e) => e.message).join("; "));
124
+ }
125
+ if (!json.data) {
126
+ throw new ShopifyApiError("No data in GraphQL response");
127
+ }
128
+ return json.data;
129
+ }
130
+ catch (e) {
131
+ lastErr = e;
132
+ if (attempt < 3) {
133
+ await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
134
+ }
135
+ }
136
+ }
137
+ throw lastErr;
138
+ }
139
+ /**
140
+ * Run a ShopifyQL query. Results cached 5 minutes — repeat queries from the
141
+ * LLM (common when it's iterating an analysis) skip the API round-trip.
142
+ */
143
+ export async function shopifyQL(ql) {
144
+ const key = `ql:${ql}`;
145
+ const cached = qlCache.get(key);
146
+ if (cached)
147
+ return cached;
148
+ const data = await shopifyGql(`query ShopifyQL($q: String!) {
149
+ shopifyqlQuery(query: $q) {
150
+ tableData { columns { name dataType displayName } rows }
151
+ parseErrors
152
+ }
153
+ }`, { variables: { q: ql }, estimatedCost: 5 });
154
+ if (data.shopifyqlQuery.parseErrors?.length) {
155
+ throw new ShopifyApiError(`ShopifyQL parse error: ${data.shopifyqlQuery.parseErrors.join(", ")}`);
156
+ }
157
+ if (!data.shopifyqlQuery.tableData) {
158
+ throw new ShopifyApiError("No table data");
159
+ }
160
+ const result = {
161
+ columns: data.shopifyqlQuery.tableData.columns,
162
+ rows: data.shopifyqlQuery.tableData.rows,
163
+ };
164
+ qlCache.set(key, result);
165
+ return result;
166
+ }
167
+ let tzCache = null;
168
+ export async function shopifyTimezone() {
169
+ if (tzCache)
170
+ return tzCache;
171
+ const data = await shopifyGql(`{ shop { ianaTimezone } }`, { estimatedCost: 1 });
172
+ tzCache = data.shop.ianaTimezone;
173
+ return tzCache;
174
+ }
175
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../../src/sdk/shopify/host.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { registerKlaviyoTools } from "./platforms/klaviyo/tools.js";
3
- import { registerShopifyTools } from "./platforms/shopify/tools.js";
4
- import { registerCrossPlatformTools } from "./cross-platform/tools.js";
2
+ import { registerExecuteCode } from "./tools/execute_code.js";
3
+ import { registerSearchDocs } from "./tools/search_docs.js";
4
+ import { registerReadDoc } from "./tools/read_doc.js";
5
5
  export function createServer() {
6
6
  const server = new McpServer({
7
7
  name: "dtc-mcp",
8
- version: "0.2.0",
8
+ version: "1.0.0",
9
9
  });
10
- registerKlaviyoTools(server);
11
- registerShopifyTools(server);
12
- registerCrossPlatformTools(server);
10
+ registerExecuteCode(server);
11
+ registerSearchDocs(server);
12
+ registerReadDoc(server);
13
13
  return server;
14
14
  }
15
15
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAEvE,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,0BAA0B,CAAC,MAAM,CAAC,CAAC;IAEnC,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,eAAe,CAAC,MAAM,CAAC,CAAC;IAExB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,4 +1,3 @@
1
- import type { ToolResponse } from "./types.js";
2
1
  export declare class KlaviyoApiError extends Error {
3
2
  status: number;
4
3
  constructor(status: number, message: string);
@@ -10,16 +9,3 @@ export declare class ShopifyApiError extends Error {
10
9
  export declare class ConfigError extends Error {
11
10
  constructor(message: string);
12
11
  }
13
- /**
14
- * Format any error into an MCP tool error response.
15
- * All errors are actionable — they include what the user needs to do.
16
- */
17
- export declare function formatError(error: unknown): ToolResponse;
18
- /**
19
- * Return a "not configured" error for Shopify tools when credentials are missing.
20
- */
21
- export declare function shopifyNotConfigured(): ToolResponse;
22
- /**
23
- * Create a successful tool response from a JSON-serializable value.
24
- */
25
- export declare function toolResult(data: unknown): ToolResponse;
@@ -20,77 +20,4 @@ export class ConfigError extends Error {
20
20
  this.name = "ConfigError";
21
21
  }
22
22
  }
23
- /**
24
- * Format any error into an MCP tool error response.
25
- * All errors are actionable — they include what the user needs to do.
26
- */
27
- export function formatError(error) {
28
- if (error instanceof KlaviyoApiError) {
29
- let advice = "";
30
- if (error.status === 401) {
31
- advice =
32
- " Check that KLAVIYO_API_KEY is a valid private key (starts with pk_).";
33
- }
34
- else if (error.status === 429) {
35
- advice = " Rate limited. Try again in a few seconds.";
36
- }
37
- else if (error.status === 403) {
38
- advice =
39
- " Your API key may not have the required scopes. Check Klaviyo API key permissions.";
40
- }
41
- return {
42
- content: [
43
- {
44
- type: "text",
45
- text: `Klaviyo API Error (${error.status}): ${error.message}${advice}`,
46
- },
47
- ],
48
- isError: true,
49
- };
50
- }
51
- if (error instanceof ShopifyApiError) {
52
- return {
53
- content: [
54
- {
55
- type: "text",
56
- text: `Shopify API Error: ${error.message}`,
57
- },
58
- ],
59
- isError: true,
60
- };
61
- }
62
- if (error instanceof ConfigError) {
63
- return {
64
- content: [{ type: "text", text: error.message }],
65
- isError: true,
66
- };
67
- }
68
- const msg = error instanceof Error ? error.message : String(error);
69
- return {
70
- content: [{ type: "text", text: `Error: ${msg}` }],
71
- isError: true,
72
- };
73
- }
74
- /**
75
- * Return a "not configured" error for Shopify tools when credentials are missing.
76
- */
77
- export function shopifyNotConfigured() {
78
- return {
79
- content: [
80
- {
81
- type: "text",
82
- text: "Shopify not configured. Set SHOPIFY_STORE + SHOPIFY_CLIENT_ID + SHOPIFY_CLIENT_SECRET (Dev Dashboard app), or SHOPIFY_STORE + SHOPIFY_ACCESS_TOKEN (legacy app).",
83
- },
84
- ],
85
- isError: true,
86
- };
87
- }
88
- /**
89
- * Create a successful tool response from a JSON-serializable value.
90
- */
91
- export function toolResult(data) {
92
- return {
93
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
94
- };
95
- }
96
23
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE/B;IADT,YACS,MAAc,EACrB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,WAAM,GAAN,MAAM,CAAQ;QAIrB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAG/B;IAFT,YACE,OAAe,EACR,UAAoC;QAE3C,KAAK,CAAC,OAAO,CAAC,CAAC;QAFR,eAAU,GAAV,UAAU,CAA0B;QAG3C,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACzB,MAAM;gBACJ,uEAAuE,CAAC;QAC5E,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAChC,MAAM,GAAG,4CAA4C,CAAC;QACxD,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAChC,MAAM;gBACJ,oFAAoF,CAAC;QACzF,CAAC;QACD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,sBAAsB,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,OAAO,GAAG,MAAM,EAAE;iBACvE;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,sBAAsB,KAAK,CAAC,OAAO,EAAE;iBAC5C;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;QACjC,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAChD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,EAAE,EAAE,CAAC;QAClD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,kKAAkK;aACzK;SACF;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/shared/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE/B;IADT,YACS,MAAc,EACrB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,WAAM,GAAN,MAAM,CAAQ;QAIrB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAG/B;IAFT,YACE,OAAe,EACR,UAAoC;QAE3C,KAAK,CAAC,OAAO,CAAC,CAAC;QAFR,eAAU,GAAV,UAAU,CAA0B;QAG3C,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF"}
@@ -1,2 +1,2 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- export declare function registerKlaviyoTools(server: McpServer): void;
2
+ export declare function registerExecuteCode(server: McpServer): void;
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ import { runSandbox } from "../sandbox/runner.js";
3
+ import { resolveTimeout } from "../sandbox/timeout.js";
4
+ import { log } from "../config.js";
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."),
7
+ };
8
+ const description = `
9
+ Execute JavaScript against the typed Klaviyo + Shopify SDKs in a stateful V8 sandbox.
10
+
11
+ The host applies rate limits, auth, and caching transparently. The sandbox keeps one
12
+ context alive per MCP connection — variables you assign to globalThis persist across
13
+ calls, so iterative analyses don't re-fetch.
14
+
15
+ Available globals:
16
+ - klaviyo: { get, post, paginate, campaigns, flows, lists, segments, profiles, events, metrics, reporting }
17
+ - shopify: { gql, ql, timezone } — Shopify Admin GraphQL + ShopifyQL
18
+ - console: { log, error, warn, info } — captured and returned as stdout
19
+ - pick(value, schema) / topN(arr, n, by) / summarize(arr, opts) — output-discipline helpers
20
+ - globalThis.* — assignments persist across calls within this MCP session
21
+
22
+ Discovery: use read_doc({}) at session start to list every available SDK path, then
23
+ read_doc({ path }) for any chunk's signature and example. Use search_docs for
24
+ intent-based queries when the path is not known.
25
+
26
+ The host caps return values at ~100 KB; oversized returns are replaced with a
27
+ truncation envelope. Use pick/topN/summarize to stay under the cap.
28
+
29
+ Example (top 5 campaigns by revenue last 30d):
30
+ const metricId = await klaviyo.getConversionMetricId();
31
+ const report = await klaviyo.reporting.campaignValues({
32
+ data: { type: "campaign-values-report", attributes: {
33
+ timeframe: { key: "last_30_days" },
34
+ conversion_metric_id: metricId,
35
+ statistics: ["recipients", "open_rate", "click_rate", "conversion_value"],
36
+ }}
37
+ });
38
+ return topN(report.data.attributes.results, 5, (r) => r.statistics.conversion_value);
39
+ `.trim();
40
+ export function registerExecuteCode(server) {
41
+ server.tool("execute_code", description, codeShape, {
42
+ title: "Execute code (sandboxed)",
43
+ readOnlyHint: false,
44
+ destructiveHint: false,
45
+ idempotentHint: false,
46
+ openWorldHint: true,
47
+ }, async ({ code }) => {
48
+ const timeoutMs = resolveTimeout(code);
49
+ log("debug", "execute_code", { length: code.length, timeoutMs });
50
+ const result = await runSandbox(code, { timeoutMs });
51
+ const text = JSON.stringify({
52
+ ok: result.ok,
53
+ ...(result.ok ? { result: result.result } : { error: result.error }),
54
+ stdout: result.stdout,
55
+ durationMs: result.durationMs,
56
+ sandbox: result.sandbox,
57
+ ...(result.sessionReset
58
+ ? {
59
+ sessionReset: true,
60
+ sessionResetNote: "This is the first execute_code call in this MCP session (or the sandbox was idle >30 min). The sandbox context is FRESH — there is no prior globalThis state to recover. From this call forward, anything you assign to globalThis WILL persist into the next execute_code call. No fallback fetches needed.",
61
+ }
62
+ : {}),
63
+ }, null, 2);
64
+ return {
65
+ content: [{ type: "text", text }],
66
+ isError: !result.ok,
67
+ };
68
+ });
69
+ }
70
+ //# sourceMappingURL=execute_code.js.map