@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.
- package/dist/index.js +139 -64
- 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
|
-
|
|
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: ["
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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:
|
|
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
|
-
|
|
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]
|
|
290
|
-
filesModified.push({ file: config.envFile, action: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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: "
|
|
1704
|
+
name: "API key configured",
|
|
1630
1705
|
passed: false,
|
|
1631
|
-
detail: "No EasyFunnel API key found in
|
|
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("
|
|
1733
|
-
|
|
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 += `
|
|
1810
|
+
output += `Alternatively, add it to an env file:
|
|
1738
1811
|
`;
|
|
1739
|
-
|
|
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":
|