@easyfunnel/mcp 0.1.11 → 0.2.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 (2) hide show
  1. package/dist/index.js +410 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -241,6 +241,58 @@ var frameworkConfigs = {
241
241
  layoutPaths: ["src/routes/+layout.svelte"]
242
242
  }
243
243
  };
244
+ var helperPaths = {
245
+ nextjs: {
246
+ helperFile: "lib/easyfunnel.ts",
247
+ bridgeFile: "components/ef-bridge.tsx",
248
+ helperImport: "@/lib/easyfunnel",
249
+ bridgeImport: "@/components/ef-bridge"
250
+ },
251
+ vite: {
252
+ helperFile: "src/lib/easyfunnel.ts",
253
+ bridgeFile: "src/components/ef-bridge.tsx",
254
+ helperImport: "@/lib/easyfunnel",
255
+ bridgeImport: "@/components/ef-bridge"
256
+ },
257
+ cra: {
258
+ helperFile: "src/lib/easyfunnel.ts",
259
+ bridgeFile: "src/components/ef-bridge.tsx",
260
+ helperImport: "@/lib/easyfunnel",
261
+ bridgeImport: "@/components/ef-bridge"
262
+ }
263
+ };
264
+ var HELPER_FILE_CONTENT = `import type { Tracker } from '@easyfunnel/sdk'
265
+
266
+ let tracker: Tracker | null = null
267
+
268
+ export function setTracker(t: Tracker | null) { tracker = t }
269
+ export function getTracker(): Tracker | null { return tracker }
270
+
271
+ export function track(event: string, properties?: Record<string, any>) {
272
+ tracker?.track(event, properties)
273
+ }
274
+
275
+ export function identify(userId: string) {
276
+ tracker?.identify(userId)
277
+ }
278
+ `;
279
+ function getBridgeFileContent(helperImport) {
280
+ return `'use client'
281
+
282
+ import { useEffect } from 'react'
283
+ import { useEasyFunnel } from '@easyfunnel/react'
284
+ import { setTracker } from '${helperImport}'
285
+
286
+ export function EfBridge() {
287
+ const tracker = useEasyFunnel()
288
+ useEffect(() => {
289
+ setTracker(tracker)
290
+ return () => setTracker(null)
291
+ }, [tracker])
292
+ return null
293
+ }
294
+ `;
295
+ }
244
296
  function detectPackageManager(projectRoot) {
245
297
  if ((0, import_fs.existsSync)((0, import_path.join)(projectRoot, "bun.lockb"))) return "bun";
246
298
  if ((0, import_fs.existsSync)((0, import_path.join)(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
@@ -398,6 +450,62 @@ Next: After adding the script, I'll verify everything works with a test event.`
398
450
  if (!providerWrapped) {
399
451
  steps.push(`[skip] Could not find layout file to add provider. Searched: ${config.layoutPaths.join(", ")}`);
400
452
  }
453
+ const paths = framework !== "html" && framework !== "sveltekit" ? helperPaths[framework] : null;
454
+ if (paths) {
455
+ const helperFullPath = (0, import_path.join)(project_root, paths.helperFile);
456
+ const helperDir = (0, import_path.dirname)(helperFullPath);
457
+ if ((0, import_fs.existsSync)(helperFullPath)) {
458
+ const existing = (0, import_fs.readFileSync)(helperFullPath, "utf-8");
459
+ if (existing.includes("setTracker")) {
460
+ steps.push(`[skip] ${paths.helperFile} already exists`);
461
+ } else {
462
+ steps.push(`[skip] ${paths.helperFile} exists but may need manual update \u2014 expected setTracker export`);
463
+ }
464
+ } else {
465
+ if (!(0, import_fs.existsSync)(helperDir)) {
466
+ (0, import_fs.mkdirSync)(helperDir, { recursive: true });
467
+ }
468
+ (0, import_fs.writeFileSync)(helperFullPath, HELPER_FILE_CONTENT);
469
+ steps.push(`[done] Generated ${paths.helperFile} (tracking helper for non-React files)`);
470
+ filesModified.push({ file: paths.helperFile, action: "Generated tracking helper" });
471
+ }
472
+ const bridgeFullPath = (0, import_path.join)(project_root, paths.bridgeFile);
473
+ const bridgeDir = (0, import_path.dirname)(bridgeFullPath);
474
+ if ((0, import_fs.existsSync)(bridgeFullPath)) {
475
+ steps.push(`[skip] ${paths.bridgeFile} already exists`);
476
+ } else {
477
+ if (!(0, import_fs.existsSync)(bridgeDir)) {
478
+ (0, import_fs.mkdirSync)(bridgeDir, { recursive: true });
479
+ }
480
+ (0, import_fs.writeFileSync)(bridgeFullPath, getBridgeFileContent(paths.helperImport));
481
+ steps.push(`[done] Generated ${paths.bridgeFile} (syncs React context to helper)`);
482
+ filesModified.push({ file: paths.bridgeFile, action: "Generated bridge component" });
483
+ }
484
+ if (providerWrapped) {
485
+ for (const relPath of config.layoutPaths) {
486
+ const fullPath = (0, import_path.join)(project_root, relPath);
487
+ if (!(0, import_fs.existsSync)(fullPath)) continue;
488
+ let content = (0, import_fs.readFileSync)(fullPath, "utf-8");
489
+ if (!content.includes("EasyFunnelProvider")) continue;
490
+ if (content.includes("EfBridge")) {
491
+ steps.push(`[skip] EfBridge already present in ${relPath}`);
492
+ break;
493
+ }
494
+ const bridgeImportLine = `import { EfBridge } from '${paths.bridgeImport}'
495
+ `;
496
+ content = bridgeImportLine + content;
497
+ content = content.replace(
498
+ /(<EasyFunnelProvider[^>]*>)([\s\S]*?)(\{children\})/,
499
+ `$1$2<EfBridge />
500
+ $3`
501
+ );
502
+ (0, import_fs.writeFileSync)(fullPath, content);
503
+ steps.push(`[done] Added <EfBridge /> to ${relPath}`);
504
+ filesModified.push({ file: relPath, action: "Added bridge component" });
505
+ break;
506
+ }
507
+ }
508
+ }
401
509
  let output = `SDK SETUP COMPLETE
402
510
 
403
511
  `;
@@ -428,6 +536,23 @@ The API key is hardcoded in your layout file \u2014 no env var configuration nee
428
536
  `;
429
537
  output += `If you prefer, you can override it via ${config.envVarName} in ${config.envFile}.
430
538
  `;
539
+ if (paths) {
540
+ output += `
541
+ Tracking helper: ${paths.helperFile}
542
+ `;
543
+ output += ` - React components: use useTrack() from @easyfunnel/react
544
+ `;
545
+ output += ` - Non-React files: import { track } from '${paths.helperImport}'
546
+ `;
547
+ } else if (framework === "html") {
548
+ output += `
549
+ Tracking: use window.EasyFunnel.track() \u2014 the SDK global is available after page load.
550
+ `;
551
+ } else if (framework === "sveltekit") {
552
+ output += `
553
+ Note: Tracking outside Svelte components requires a manual singleton pattern.
554
+ `;
555
+ }
431
556
  output += `
432
557
  Next: I'll verify everything works with a test event.`;
433
558
  return {
@@ -565,9 +690,22 @@ async function scanForActions(args) {
565
690
 
566
691
  // src/tools/instrument-code.ts
567
692
  var import_fs3 = require("fs");
693
+
694
+ // src/lib/react-detect.ts
695
+ function isReactComponent(filePath, content) {
696
+ const ext = filePath.split(".").pop()?.toLowerCase();
697
+ if (ext !== "tsx" && ext !== "jsx") return false;
698
+ return content.includes("'use client'") || content.includes('"use client"') || /import\s+React/m.test(content) || /from\s+['"]react['"]/m.test(content) || /return\s*\(/m.test(content);
699
+ }
700
+ function isReactAware(filePath, content) {
701
+ if (isReactComponent(filePath, content)) return true;
702
+ return /from\s+['"]react['"]/m.test(content) || /from\s+['"]@easyfunnel\/react['"]/m.test(content);
703
+ }
704
+
705
+ // src/tools/instrument-code.ts
568
706
  var instrumentCodeDefinition = {
569
707
  name: "instrument_code",
570
- description: "Add easyfunnel tracking to a specific interactive element in the codebase. Returns a diff preview \u2014 does NOT auto-save.",
708
+ description: "Add easyfunnel tracking to a specific interactive element. attribute method returns a diff preview. track_call method returns context-aware suggestions (useTrack for React components, helper import for other files). Does NOT auto-save.",
571
709
  inputSchema: {
572
710
  type: "object",
573
711
  properties: {
@@ -586,7 +724,7 @@ var instrumentCodeDefinition = {
586
724
  method: {
587
725
  type: "string",
588
726
  enum: ["attribute", "track_call"],
589
- description: "attribute: add data-ef-track attribute. track_call: add ef.track() call inside handler."
727
+ description: "attribute: add data-ef-track attribute. track_call: add track() call \u2014 automatically suggests useTrack() for React components or import from lib/easyfunnel for other files."
590
728
  }
591
729
  },
592
730
  required: ["file", "line", "event_name", "method"]
@@ -607,8 +745,6 @@ async function instrumentCode(args) {
607
745
  ]
608
746
  };
609
747
  }
610
- let newLine;
611
- let changeDescription;
612
748
  if (method === "attribute") {
613
749
  if (targetLine.includes("data-ef-track")) {
614
750
  return {
@@ -620,54 +756,98 @@ async function instrumentCode(args) {
620
756
  ]
621
757
  };
622
758
  }
623
- newLine = targetLine.replace(
759
+ let newLine = targetLine.replace(
624
760
  /(<\w+)(\s)/,
625
761
  `$1 data-ef-track="${event_name}"$2`
626
762
  );
627
763
  if (newLine === targetLine) {
628
764
  newLine = targetLine.replace(/>/, ` data-ef-track="${event_name}">`);
629
765
  }
630
- changeDescription = `Added data-ef-track="${event_name}" attribute`;
631
- } else {
632
- newLine = targetLine;
633
- if (targetLine.includes("onClick") || targetLine.includes("onSubmit")) {
634
- changeDescription = `Add track('${event_name}') call. Suggested modification:
766
+ const changeDescription2 = `Added data-ef-track="${event_name}" attribute`;
767
+ const diff = `--- ${file}
768
+ +++ ${file}
769
+ @@ -${line},1 +${line},1 @@
770
+ -${targetLine}
771
+ +${newLine}`;
772
+ return {
773
+ content: [
774
+ {
775
+ type: "text",
776
+ text: `${changeDescription2}
777
+
778
+ Diff preview:
779
+ \`\`\`diff
780
+ ${diff}
781
+ \`\`\`
782
+
783
+ Apply this change to ${file} at line ${line}.`
784
+ }
785
+ ]
786
+ };
787
+ }
788
+ const isReact = isReactComponent(file, content);
789
+ const hasHandler = targetLine.includes("onClick") || targetLine.includes("onSubmit");
790
+ const handlerHint = hasHandler ? `
791
+
792
+ The target line contains an event handler \u2014 add the track call at the start of that handler function.` : "";
793
+ if (!isReact && isReactAware(file, content)) {
794
+ const changeDescription2 = `Add track('${event_name}') call (React-aware file detected \u2014 could be a hook or utility).
795
+
796
+ Option A (if this is a React hook/component):
797
+ 1. import { useTrack } from '@easyfunnel/react'
798
+ 2. const track = useTrack()
799
+ 3. track('${event_name}')
800
+
801
+ Option B (if this is a utility/helper):
802
+ 1. import { track } from '@/lib/easyfunnel'
803
+ 2. track('${event_name}')` + handlerHint;
804
+ return {
805
+ content: [
806
+ {
807
+ type: "text",
808
+ text: `Instrumentation suggestion for ${file}:${line}
809
+
810
+ ${changeDescription2}
811
+
812
+ Please apply the appropriate option using your code editing capabilities.`
813
+ }
814
+ ]
815
+ };
816
+ }
817
+ if (isReact) {
818
+ const changeDescription2 = `Add track('${event_name}') call (React component detected).
635
819
 
636
820
  1. Add import: import { useTrack } from '@easyfunnel/react'
637
- 2. Add hook: const track = useTrack()
638
- 3. Add track call: track('${event_name}') at the start of the handler function`;
639
- return {
640
- content: [
641
- {
642
- type: "text",
643
- text: `Instrumentation suggestion for ${file}:${line}
821
+ 2. Add hook: const track = useTrack()
822
+ 3. Add call: track('${event_name}') at the start of the handler` + handlerHint;
823
+ return {
824
+ content: [
825
+ {
826
+ type: "text",
827
+ text: `Instrumentation suggestion for ${file}:${line}
644
828
 
645
- ${changeDescription}
829
+ ${changeDescription2}
646
830
 
647
831
  This requires modifying the component. Please apply these changes using your code editing capabilities.`
648
- }
649
- ]
650
- };
651
- }
652
- changeDescription = `Suggested: add track('${event_name}') call in the handler near line ${line}`;
832
+ }
833
+ ]
834
+ };
653
835
  }
654
- const diff = `--- ${file}
655
- +++ ${file}
656
- @@ -${line},1 +${line},1 @@
657
- -${targetLine}
658
- +${newLine}`;
836
+ const changeDescription = `Add track('${event_name}') call (non-React file detected).
837
+
838
+ 1. Add import: import { track } from '@/lib/easyfunnel'
839
+ 2. Add call: track('${event_name}') at the relevant point` + handlerHint + `
840
+
841
+ Note: lib/easyfunnel.ts is generated by setup_sdk. If it doesn't exist, run setup_sdk first.`;
659
842
  return {
660
843
  content: [
661
844
  {
662
845
  type: "text",
663
- text: `${changeDescription}
846
+ text: `Instrumentation suggestion for ${file}:${line}
664
847
 
665
- Diff preview:
666
- \`\`\`diff
667
- ${diff}
668
- \`\`\`
848
+ ${changeDescription}
669
849
 
670
- Apply this change to ${file} at line ${line}.`
850
+ Please apply these changes using your code editing capabilities.`
671
851
  }
672
852
  ]
673
853
  };
@@ -1718,6 +1898,69 @@ function findHardcodedKey(projectRoot) {
1718
1898
  }
1719
1899
  return null;
1720
1900
  }
1901
+ function scanAntiPatterns(projectRoot, isReactFramework) {
1902
+ const warnings = [];
1903
+ const files = walkDir2(projectRoot, [".ts", ".tsx", ".js", ".jsx"], [], 300);
1904
+ const patterns = [
1905
+ {
1906
+ id: "window_ef",
1907
+ regex: /window\.ef(?![\w.])/,
1908
+ condition: () => true,
1909
+ message: "window.ef doesn't exist. Import { track } from '@/lib/easyfunnel' instead."
1910
+ },
1911
+ {
1912
+ id: "global_track",
1913
+ regex: /window\.EasyFunnel\.track/,
1914
+ condition: () => isReactFramework,
1915
+ message: "Direct global access is fragile in React apps. Import { track } from '@/lib/easyfunnel' instead."
1916
+ },
1917
+ {
1918
+ id: "hook_outside_component",
1919
+ regex: /useTrack\(\)/,
1920
+ condition: (filePath, content) => !isReactComponent(filePath, content),
1921
+ message: "useTrack() only works inside React components. Use import { track } from '@/lib/easyfunnel' for non-component files."
1922
+ }
1923
+ ];
1924
+ for (const file of files) {
1925
+ const content = readFileSafe(file);
1926
+ if (!content) continue;
1927
+ const relPath = file.replace(projectRoot + "/", "");
1928
+ const lines = content.split("\n");
1929
+ for (const pattern of patterns) {
1930
+ if (!pattern.condition(file, content)) continue;
1931
+ for (let i = 0; i < lines.length; i++) {
1932
+ if (pattern.regex.test(lines[i])) {
1933
+ warnings.push({
1934
+ file: relPath,
1935
+ line: i + 1,
1936
+ id: pattern.id,
1937
+ message: pattern.message
1938
+ });
1939
+ }
1940
+ }
1941
+ }
1942
+ }
1943
+ if (isReactFramework) {
1944
+ const helperExists = (0, import_fs6.existsSync)((0, import_path4.join)(projectRoot, "lib/easyfunnel.ts")) || (0, import_fs6.existsSync)((0, import_path4.join)(projectRoot, "src/lib/easyfunnel.ts"));
1945
+ if (!helperExists) {
1946
+ const hasTrackingOutsideReact = files.some((f) => {
1947
+ const content = readFileSafe(f);
1948
+ if (!content) return false;
1949
+ if (isReactComponent(f, content)) return false;
1950
+ return content.includes(".track(") || content.includes("window.ef") || content.includes("window.EasyFunnel");
1951
+ });
1952
+ if (hasTrackingOutsideReact) {
1953
+ warnings.push({
1954
+ file: "",
1955
+ line: 0,
1956
+ id: "missing_helper",
1957
+ message: "No tracking helper found. Run setup_sdk to generate lib/easyfunnel.ts."
1958
+ });
1959
+ }
1960
+ }
1961
+ }
1962
+ return warnings;
1963
+ }
1721
1964
  async function validateSetup(client2, args) {
1722
1965
  const { project_root, project_id } = args;
1723
1966
  const checks = [];
@@ -1817,6 +2060,13 @@ async function validateSetup(client2, args) {
1817
2060
  });
1818
2061
  }
1819
2062
  }
2063
+ const isReactFramework = (() => {
2064
+ const pkgPath = (0, import_path4.join)(project_root, "package.json");
2065
+ if (!(0, import_fs6.existsSync)(pkgPath)) return false;
2066
+ const pkg = (0, import_fs6.readFileSync)(pkgPath, "utf-8");
2067
+ return pkg.includes('"react"') || pkg.includes("'react'");
2068
+ })();
2069
+ const antiPatterns = scanAntiPatterns(project_root, isReactFramework);
1820
2070
  const allPassed = checks.every((c) => c.passed);
1821
2071
  const failedChecks = checks.filter((c) => !c.passed);
1822
2072
  let output = "";
@@ -1866,6 +2116,22 @@ Next: Let me suggest conversion funnels based on what I found in your codebase.`
1866
2116
  }
1867
2117
  }
1868
2118
  }
2119
+ if (antiPatterns.length > 0) {
2120
+ output += `
2121
+ Recommendations:
2122
+ `;
2123
+ for (const w of antiPatterns) {
2124
+ if (w.file) {
2125
+ output += ` [WARN] ${w.file}:${w.line} \u2014 ${w.message}
2126
+ `;
2127
+ } else {
2128
+ output += ` [WARN] ${w.message}
2129
+ `;
2130
+ }
2131
+ }
2132
+ output += `
2133
+ `;
2134
+ }
1869
2135
  return {
1870
2136
  content: [{ type: "text", text: output }]
1871
2137
  };
@@ -2239,6 +2505,112 @@ ${msg.content}
2239
2505
  };
2240
2506
  }
2241
2507
 
2508
+ // src/tools/get-best-practices.ts
2509
+ var getBestPracticesDefinition = {
2510
+ name: "get_best_practices",
2511
+ description: "Get framework-specific tracking best practices. Call this BEFORE writing any tracking code to avoid common integration mistakes.",
2512
+ inputSchema: {
2513
+ type: "object",
2514
+ properties: {
2515
+ framework: {
2516
+ type: "string",
2517
+ enum: ["nextjs", "vite", "cra", "sveltekit", "html"],
2518
+ description: "The framework. If omitted, returns general guidance for all frameworks."
2519
+ }
2520
+ },
2521
+ required: []
2522
+ }
2523
+ };
2524
+ var TRACKING_PATTERNS = `TRACKING PATTERNS \u2014 How to track events
2525
+
2526
+ React components:
2527
+ import { useTrack } from '@easyfunnel/react'
2528
+ const track = useTrack()
2529
+ track('event_name', { key: 'value' })
2530
+
2531
+ Non-React files (utils, API handlers, state stores, callbacks):
2532
+ import { track } from '@/lib/easyfunnel'
2533
+ track('event_name', { key: 'value' })
2534
+
2535
+ HTML / script-tag sites:
2536
+ window.EasyFunnel.track('event_name', { key: 'value' })
2537
+
2538
+ Declarative (zero code \u2014 just add an attribute):
2539
+ <button data-ef-track="click_signup">Sign Up</button>`;
2540
+ var COMMON_MISTAKES = `COMMON MISTAKES \u2014 What NOT to do
2541
+
2542
+ 1. window.ef does NOT exist.
2543
+ The SDK never sets window.ef. If you see code polling for it, remove it.
2544
+ Use: import { track } from '@/lib/easyfunnel'
2545
+
2546
+ 2. useTrack() outside React components silently drops events.
2547
+ Hooks require React context. In plain .ts files, use the helper module.
2548
+ Use: import { track } from '@/lib/easyfunnel'
2549
+
2550
+ 3. window.EasyFunnel.track() in React apps is fragile.
2551
+ It bypasses the React lifecycle and may fire before SDK initializes.
2552
+ Use: useTrack() in components, or import { track } from '@/lib/easyfunnel'
2553
+
2554
+ 4. Custom global polling/bridging code is unnecessary.
2555
+ setup_sdk generates lib/easyfunnel.ts and a bridge component automatically.
2556
+ Do NOT write your own window.* bridge \u2014 use the generated helper.`;
2557
+ var CHECKLISTS = {
2558
+ nextjs: `SETUP CHECKLIST \u2014 Next.js
2559
+
2560
+ [1] EasyFunnelProvider wraps {children} in app/layout.tsx
2561
+ [2] EfBridge component rendered inside the provider (syncs context to helper)
2562
+ [3] lib/easyfunnel.ts exists (generated by setup_sdk)
2563
+ [4] Components using useTrack() have 'use client' directive
2564
+ [5] API key set via NEXT_PUBLIC_EASYFUNNEL_KEY in .env.local`,
2565
+ vite: `SETUP CHECKLIST \u2014 Vite
2566
+
2567
+ [1] EasyFunnelProvider wraps the app in src/App.tsx or src/main.tsx
2568
+ [2] EfBridge component rendered inside the provider
2569
+ [3] src/lib/easyfunnel.ts exists (generated by setup_sdk)
2570
+ [4] API key set via VITE_EASYFUNNEL_KEY in .env`,
2571
+ cra: `SETUP CHECKLIST \u2014 Create React App
2572
+
2573
+ [1] EasyFunnelProvider wraps the app in src/App.tsx
2574
+ [2] EfBridge component rendered inside the provider
2575
+ [3] src/lib/easyfunnel.ts exists (generated by setup_sdk)
2576
+ [4] API key set via REACT_APP_EASYFUNNEL_KEY in .env`,
2577
+ sveltekit: `SETUP CHECKLIST \u2014 SvelteKit
2578
+
2579
+ [1] SDK loaded via <script> tag in +layout.svelte (React provider does NOT work in Svelte)
2580
+ [2] Tracking outside components requires a manual singleton pattern
2581
+ [3] API key set via PUBLIC_EASYFUNNEL_KEY in .env`,
2582
+ html: `SETUP CHECKLIST \u2014 HTML / Script Tag
2583
+
2584
+ [1] <script defer data-api-key="ef_..." src="https://easyfunnel.co/sdk.js"></script> in <head>
2585
+ [2] Track via window.EasyFunnel.track() after page load
2586
+ [3] No helper module needed \u2014 window.EasyFunnel IS the global`
2587
+ };
2588
+ var GENERAL_CHECKLIST = `SETUP CHECKLIST \u2014 General
2589
+
2590
+ React frameworks (Next.js, Vite, CRA):
2591
+ [1] EasyFunnelProvider wraps the app
2592
+ [2] EfBridge component rendered inside the provider
2593
+ [3] lib/easyfunnel.ts helper exists for non-component tracking
2594
+ [4] Next.js: 'use client' on components using hooks
2595
+
2596
+ HTML / Script Tag:
2597
+ [1] SDK script tag in <head>
2598
+ [2] Track via window.EasyFunnel.track()
2599
+
2600
+ Pass framework parameter for a framework-specific checklist.`;
2601
+ async function getBestPractices(args) {
2602
+ const { framework } = args;
2603
+ let output = TRACKING_PATTERNS + "\n\n" + COMMON_MISTAKES + "\n\n";
2604
+ if (framework && CHECKLISTS[framework]) {
2605
+ output += CHECKLISTS[framework];
2606
+ } else {
2607
+ output += GENERAL_CHECKLIST;
2608
+ }
2609
+ return {
2610
+ content: [{ type: "text", text: output }]
2611
+ };
2612
+ }
2613
+
2242
2614
  // src/index.ts
2243
2615
  var apiKey = process.env.EASYFUNNEL_API_KEY;
2244
2616
  if (!apiKey) {
@@ -2270,7 +2642,8 @@ server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({
2270
2642
  deleteFunnelDefinition,
2271
2643
  updateFunnelDefinition,
2272
2644
  setupChatWidgetDefinition,
2273
- queryConversationsDefinition
2645
+ queryConversationsDefinition,
2646
+ getBestPracticesDefinition
2274
2647
  ]
2275
2648
  }));
2276
2649
  server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
@@ -2308,6 +2681,8 @@ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) =>
2308
2681
  return setupChatWidget(client, args);
2309
2682
  case "query_conversations":
2310
2683
  return queryConversations(client, args);
2684
+ case "get_best_practices":
2685
+ return getBestPractices(args);
2311
2686
  default:
2312
2687
  throw new Error(`Unknown tool: ${name}`);
2313
2688
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easyfunnel/mcp",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for easyfunnel.co — AI-powered analytics tools for Claude/Cursor",
5
5
  "main": "dist/index.js",
6
6
  "bin": {