@easyfunnel/mcp 0.1.7 → 0.1.9

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/index.js +139 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -119,7 +119,26 @@ var listProjectsDefinition = {
119
119
  };
120
120
  async function listProjects(client2) {
121
121
  const projects = await client2.listProjects();
122
- return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
122
+ let output = "";
123
+ if (Array.isArray(projects) && projects.length > 0) {
124
+ for (const p of projects) {
125
+ output += `Project: ${p.name}
126
+ `;
127
+ output += ` ID: ${p.id}
128
+ `;
129
+ output += ` Project API Key: ${p.api_key} (use this in the SDK / env var)
130
+ `;
131
+ output += ` Domains: ${p.domain_whitelist?.length ? p.domain_whitelist.join(", ") : "(all allowed)"}
132
+
133
+ `;
134
+ }
135
+ output += `NOTE: The "Project API Key" (ef_...) goes in your app's env var for the SDK.
136
+ `;
137
+ output += `The "Account API Key" (efa_...) is only for the MCP server \u2014 never put it in your app code.`;
138
+ } else {
139
+ output = "No projects found. Use create_project to create one.";
140
+ }
141
+ return { content: [{ type: "text", text: output }] };
123
142
  }
124
143
 
125
144
  // src/tools/create-project.ts
@@ -163,13 +182,17 @@ var import_path = require("path");
163
182
  var import_child_process = require("child_process");
164
183
  var setupSdkDefinition = {
165
184
  name: "setup_sdk",
166
- description: "Install the EasyFunnel SDK, write the env var, and wrap the app with the provider. Supports Next.js, Vite, CRA, SvelteKit, and plain HTML.",
185
+ description: "Install the EasyFunnel SDK, write the env var, and wrap the app with the provider. Supports Next.js, Vite, CRA, SvelteKit, and plain HTML. Provide either project_id (recommended) or project_api_key.",
167
186
  inputSchema: {
168
187
  type: "object",
169
188
  properties: {
189
+ project_id: {
190
+ type: "string",
191
+ description: "The project ID (UUID). The tool will look up the correct project API key (ef_...) automatically. Preferred over project_api_key."
192
+ },
170
193
  project_api_key: {
171
194
  type: "string",
172
- description: "The project API key (ef_...)"
195
+ description: "The project API key (ef_...). Use project_id instead if possible \u2014 this avoids accidentally passing the wrong key."
173
196
  },
174
197
  project_root: {
175
198
  type: "string",
@@ -181,32 +204,32 @@ var setupSdkDefinition = {
181
204
  description: "The framework used in the project"
182
205
  }
183
206
  },
184
- required: ["project_api_key", "project_root", "framework"]
207
+ required: ["project_root", "framework"]
185
208
  }
186
209
  };
187
210
  var frameworkConfigs = {
188
211
  nextjs: {
189
212
  envFile: ".env.local",
190
213
  envVarName: "NEXT_PUBLIC_EASYFUNNEL_KEY",
191
- envAccessor: "process.env.NEXT_PUBLIC_EASYFUNNEL_KEY!",
214
+ envAccessor: (apiKey2) => `process.env.NEXT_PUBLIC_EASYFUNNEL_KEY || "${apiKey2}"`,
192
215
  layoutPaths: ["app/layout.tsx", "app/layout.jsx", "src/app/layout.tsx", "src/app/layout.jsx"]
193
216
  },
194
217
  vite: {
195
218
  envFile: ".env",
196
219
  envVarName: "VITE_EASYFUNNEL_KEY",
197
- envAccessor: "import.meta.env.VITE_EASYFUNNEL_KEY",
220
+ envAccessor: (apiKey2) => `import.meta.env.VITE_EASYFUNNEL_KEY || "${apiKey2}"`,
198
221
  layoutPaths: ["src/App.tsx", "src/App.jsx", "src/main.tsx", "src/main.jsx"]
199
222
  },
200
223
  cra: {
201
224
  envFile: ".env",
202
225
  envVarName: "REACT_APP_EASYFUNNEL_KEY",
203
- envAccessor: "process.env.REACT_APP_EASYFUNNEL_KEY!",
226
+ envAccessor: (apiKey2) => `process.env.REACT_APP_EASYFUNNEL_KEY || "${apiKey2}"`,
204
227
  layoutPaths: ["src/App.tsx", "src/App.jsx", "src/index.tsx", "src/index.jsx"]
205
228
  },
206
229
  sveltekit: {
207
230
  envFile: ".env",
208
231
  envVarName: "PUBLIC_EASYFUNNEL_KEY",
209
- envAccessor: "import.meta.env.PUBLIC_EASYFUNNEL_KEY",
232
+ envAccessor: (apiKey2) => `import.meta.env.PUBLIC_EASYFUNNEL_KEY || "${apiKey2}"`,
210
233
  layoutPaths: ["src/routes/+layout.svelte"]
211
234
  }
212
235
  };
@@ -216,20 +239,48 @@ function detectPackageManager(projectRoot) {
216
239
  if ((0, import_fs.existsSync)((0, import_path.join)(projectRoot, "yarn.lock"))) return "yarn";
217
240
  return "npm";
218
241
  }
219
- async function setupSdk(args) {
220
- const { project_api_key, project_root, framework } = args;
221
- if (!project_api_key?.startsWith("ef_")) {
222
- return {
223
- content: [
224
- {
242
+ async function setupSdk(client2, args) {
243
+ const { project_root, framework } = args;
244
+ let project_api_key = args.project_api_key || "";
245
+ if (args.project_id && !project_api_key) {
246
+ try {
247
+ const projects = await client2.listProjects();
248
+ const project = projects.find((p) => p.id === args.project_id);
249
+ if (project?.api_key) {
250
+ project_api_key = project.api_key;
251
+ } else {
252
+ return {
253
+ content: [{
254
+ type: "text",
255
+ text: `Error: Project ID "${args.project_id}" not found. Run list_projects to see your projects.`
256
+ }]
257
+ };
258
+ }
259
+ } catch (err) {
260
+ return {
261
+ content: [{
225
262
  type: "text",
226
- text: `Error: Invalid API key format. Project keys start with "ef_". Got: "${project_api_key || "(empty)"}"
263
+ text: `Error: Could not look up project: ${err.message}`
264
+ }]
265
+ };
266
+ }
267
+ }
268
+ if (!project_api_key?.startsWith("ef_")) {
269
+ const hint = project_api_key?.startsWith("efa_") ? `
227
270
 
228
- Account keys ("efa_") are for the MCP server, not the SDK. Use list_projects to find your project API key.`
229
- }
230
- ]
271
+ You passed an account API key (efa_...). That key is for the MCP server, not the SDK.
272
+ Use project_id instead, or run list_projects to find the correct project API key (ef_...).` : `
273
+
274
+ Use project_id instead of project_api_key \u2014 the tool will look up the correct key automatically.`;
275
+ return {
276
+ content: [{
277
+ type: "text",
278
+ text: `Error: Invalid API key format. Project keys start with "ef_". Got: "${project_api_key || "(empty)"}"
279
+ ${hint}`
280
+ }]
231
281
  };
232
282
  }
283
+ const config = framework !== "html" ? frameworkConfigs[framework] : null;
233
284
  if (framework === "html") {
234
285
  return {
235
286
  content: [
@@ -241,6 +292,9 @@ Add this script tag to your <head>:
241
292
 
242
293
  <script defer data-api-key="${project_api_key}" src="https://easyfunnel.co/sdk.js"></script>
243
294
 
295
+ Project API Key: ${project_api_key}
296
+ (This is the PROJECT key for the SDK \u2014 not the account key used by the MCP server.)
297
+
244
298
  This will automatically track page views and clicks on elements with data-ef-track attributes.
245
299
 
246
300
  Next: After adding the script, I'll verify everything works with a test event.`
@@ -248,7 +302,6 @@ Next: After adding the script, I'll verify everything works with a test event.`
248
302
  ]
249
303
  };
250
304
  }
251
- const config = frameworkConfigs[framework];
252
305
  const steps = [];
253
306
  const filesModified = [];
254
307
  const pm = detectPackageManager(project_root);
@@ -286,8 +339,8 @@ Next: After adding the script, I'll verify everything works with a test event.`
286
339
  if (envWritten) {
287
340
  const verifyContent = (0, import_fs.readFileSync)(envPath, "utf-8");
288
341
  if (verifyContent.includes(project_api_key)) {
289
- steps.push(`[done] Added ${config.envVarName}=${project_api_key.slice(0, 8)}... to ${config.envFile}`);
290
- filesModified.push({ file: config.envFile, action: "Added API key" });
342
+ steps.push(`[done] Set ${config.envVarName}=${project_api_key} in ${config.envFile}`);
343
+ filesModified.push({ file: config.envFile, action: "Set project API key" });
291
344
  } else {
292
345
  steps.push(`[FAIL] Wrote to ${config.envFile} but verification failed`);
293
346
  }
@@ -321,7 +374,7 @@ Next: After adding the script, I'll verify everything works with a test event.`
321
374
  if (content.includes("{children}")) {
322
375
  content = content.replace(
323
376
  /(\{children\})/,
324
- `<EasyFunnelProvider apiKey={${config.envAccessor}}>
377
+ `<EasyFunnelProvider apiKey={${config.envAccessor(project_api_key)}}>
325
378
  $1
326
379
  </EasyFunnelProvider>`
327
380
  );
@@ -356,10 +409,19 @@ Files modified:
356
409
  }
357
410
  }
358
411
  output += `
359
- IMPORTANT: Restart your dev server for the env var to take effect.
412
+ Project API Key: ${project_api_key}
413
+ `;
414
+ output += `Env var: ${config.envVarName}=${project_api_key}
415
+ `;
416
+ output += `(This is the PROJECT key for the SDK \u2014 not the account key used by the MCP server.)
360
417
  `;
361
418
  output += `
362
- Next: After restarting, I'll verify everything works with a test event.`;
419
+ The API key is hardcoded in your layout file \u2014 no env var configuration needed.
420
+ `;
421
+ output += `If you prefer, you can override it via ${config.envVarName} in ${config.envFile}.
422
+ `;
423
+ output += `
424
+ Next: I'll verify everything works with a test event.`;
363
425
  return {
364
426
  content: [{ type: "text", text: output }]
365
427
  };
@@ -1576,18 +1638,18 @@ function readEnvFile(projectRoot) {
1576
1638
  }
1577
1639
  return null;
1578
1640
  }
1641
+ var layoutCandidates = [
1642
+ "app/layout.tsx",
1643
+ "app/layout.jsx",
1644
+ "src/app/layout.tsx",
1645
+ "src/app/layout.jsx",
1646
+ "src/App.tsx",
1647
+ "src/App.jsx",
1648
+ "src/main.tsx",
1649
+ "src/main.jsx"
1650
+ ];
1579
1651
  function findProviderFile(projectRoot) {
1580
- const candidates = [
1581
- "app/layout.tsx",
1582
- "app/layout.jsx",
1583
- "src/app/layout.tsx",
1584
- "src/app/layout.jsx",
1585
- "src/App.tsx",
1586
- "src/App.jsx",
1587
- "src/main.tsx",
1588
- "src/main.jsx"
1589
- ];
1590
- for (const relPath of candidates) {
1652
+ for (const relPath of layoutCandidates) {
1591
1653
  const fullPath = (0, import_path4.join)(projectRoot, relPath);
1592
1654
  if ((0, import_fs6.existsSync)(fullPath)) {
1593
1655
  const content = (0, import_fs6.readFileSync)(fullPath, "utf-8");
@@ -1598,37 +1660,50 @@ function findProviderFile(projectRoot) {
1598
1660
  }
1599
1661
  return null;
1600
1662
  }
1663
+ function findHardcodedKey(projectRoot) {
1664
+ for (const relPath of layoutCandidates) {
1665
+ const fullPath = (0, import_path4.join)(projectRoot, relPath);
1666
+ if (!(0, import_fs6.existsSync)(fullPath)) continue;
1667
+ const content = (0, import_fs6.readFileSync)(fullPath, "utf-8");
1668
+ if (!content.includes("EasyFunnelProvider")) continue;
1669
+ const match = content.match(/apiKey[=:].*?(ef_[a-f0-9]+)/);
1670
+ if (match) {
1671
+ return { file: relPath, key: match[1] };
1672
+ }
1673
+ }
1674
+ return null;
1675
+ }
1601
1676
  async function validateSetup(client2, args) {
1602
1677
  const { project_root, project_id } = args;
1603
1678
  const checks = [];
1604
1679
  const envResult = readEnvFile(project_root);
1680
+ const hardcodedResult = findHardcodedKey(project_root);
1605
1681
  let apiKey2 = args.project_api_key || "";
1606
- if (envResult) {
1682
+ if (envResult && envResult.value && envResult.value.startsWith("ef_")) {
1607
1683
  apiKey2 = apiKey2 || envResult.value;
1608
- if (envResult.value && envResult.value.startsWith("ef_")) {
1609
- checks.push({
1610
- name: `${envResult.file} contains ${envResult.varName}`,
1611
- passed: true,
1612
- detail: `${envResult.varName}=${envResult.value.slice(0, 8)}...`
1613
- });
1614
- } else if (envResult.value) {
1615
- checks.push({
1616
- name: `${envResult.file} contains ${envResult.varName}`,
1617
- passed: false,
1618
- detail: `Value doesn't start with "ef_". Got: ${envResult.value.slice(0, 20)}`
1619
- });
1620
- } else {
1621
- checks.push({
1622
- name: `${envResult.file} contains ${envResult.varName}`,
1623
- passed: false,
1624
- detail: `${envResult.varName} is empty`
1625
- });
1626
- }
1684
+ checks.push({
1685
+ name: `${envResult.file} contains ${envResult.varName}`,
1686
+ passed: true,
1687
+ detail: `${envResult.varName}=${envResult.value.slice(0, 8)}...`
1688
+ });
1689
+ } else if (hardcodedResult) {
1690
+ apiKey2 = apiKey2 || hardcodedResult.key;
1691
+ checks.push({
1692
+ name: `API key found in ${hardcodedResult.file}`,
1693
+ passed: true,
1694
+ detail: `Hardcoded key: ${hardcodedResult.key.slice(0, 8)}... (recommended for static deployments)`
1695
+ });
1696
+ } else if (envResult && envResult.value) {
1697
+ checks.push({
1698
+ name: `${envResult.file} contains ${envResult.varName}`,
1699
+ passed: false,
1700
+ detail: `Value doesn't start with "ef_". Got: ${envResult.value.slice(0, 20)}`
1701
+ });
1627
1702
  } else {
1628
1703
  checks.push({
1629
- name: "Env file contains API key",
1704
+ name: "API key configured",
1630
1705
  passed: false,
1631
- detail: "No EasyFunnel API key found in .env.local or .env"
1706
+ detail: "No EasyFunnel API key found in env file or hardcoded in layout. Run setup_sdk to add it."
1632
1707
  });
1633
1708
  }
1634
1709
  const providerFile = findProviderFile(project_root);
@@ -1729,14 +1804,14 @@ Next: Let me suggest conversion funnels based on what I found in your codebase.`
1729
1804
  output += `
1730
1805
  `;
1731
1806
  for (const check of failedChecks) {
1732
- if (check.name.includes("Env file") || check.name.includes("API key")) {
1733
- const varName = envResult?.varName || "NEXT_PUBLIC_EASYFUNNEL_KEY";
1734
- const envFile = envResult?.file || ".env.local";
1735
- output += `To fix: Add your project API key to ${envFile}:
1807
+ if (check.name.includes("API key") || check.name.includes("Env file")) {
1808
+ output += `To fix: Run setup_sdk to hardcode the API key in your layout file.
1736
1809
  `;
1737
- output += ` ${varName}=ef_your_key_here
1810
+ output += `Alternatively, add it to an env file:
1738
1811
  `;
1739
- output += `Then restart your dev server.
1812
+ const varName = envResult?.varName || "NEXT_PUBLIC_EASYFUNNEL_KEY";
1813
+ const envFile = envResult?.file || ".env.local";
1814
+ output += ` ${varName}=ef_your_key_here (in ${envFile})
1740
1815
 
1741
1816
  `;
1742
1817
  } else if (check.name.includes("Provider")) {
@@ -2085,7 +2160,7 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
2085
2160
  case "scan_codebase":
2086
2161
  return scanCodebase(client, args);
2087
2162
  case "setup_sdk":
2088
- return setupSdk(args);
2163
+ return setupSdk(client, args);
2089
2164
  case "validate_setup":
2090
2165
  return validateSetup(client, args);
2091
2166
  case "recommend_funnels":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easyfunnel/mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "MCP server for easyfunnel.co — AI-powered analytics tools for Claude/Cursor",
5
5
  "main": "dist/index.js",
6
6
  "bin": {