@growthbook/mcp 0.1.2 → 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.
- package/README.md +7 -4
- package/build/index.js +1 -1
- package/build/tools/defaults.js +155 -3
- package/build/tools/experiments.js +11 -5
- package/build/tools/features.js +27 -2
- package/build/utils.js +32 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Use the following env variables to configure the MCP server.
|
|
|
14
14
|
| Variable Name | Status | Description |
|
|
15
15
|
| ------------- | -------- | ----------------------------------------------------------------- |
|
|
16
16
|
| GB_API_KEY | Required | A GrowthBook API key. |
|
|
17
|
-
|
|
|
17
|
+
| GB_EMAIL | Required | Your email address used with GrowthBook. Used when creating feature flags and experiments.|
|
|
18
18
|
| GB_API_URL | Optional | Your GrowthBook API URL. Defaults to `https://api.growthbook.io`. |
|
|
19
19
|
| GB_APP_ORIGIN | Optional | Your GrowthBook app URL Defaults to `https://app.growthbook.io`. |
|
|
20
20
|
|
|
@@ -36,7 +36,7 @@ Find instructions below to add the MCP server to a client. Any client that suppo
|
|
|
36
36
|
"GB_API_KEY": "YOUR_API_KEY",
|
|
37
37
|
"GB_API_URL": "YOUR_API_URL",
|
|
38
38
|
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
39
|
-
"
|
|
39
|
+
"GB_EMAIL": "YOUR_EMAIL"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -64,7 +64,7 @@ You should now see a green active status after the server successfully connects!
|
|
|
64
64
|
"GB_API_KEY": "YOUR_API_KEY",
|
|
65
65
|
"GB_API_URL": "YOUR_API_URL",
|
|
66
66
|
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
67
|
-
"
|
|
67
|
+
"GB_EMAIL": "YOUR_EMAIL"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -92,7 +92,7 @@ GrowthBook MCP is now ready to use in VS Code.
|
|
|
92
92
|
"GB_API_KEY": "YOUR_API_KEY",
|
|
93
93
|
"GB_API_URL": "YOUR_API_URL",
|
|
94
94
|
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
95
|
-
"
|
|
95
|
+
"GB_EMAIL": "YOUR_EMAIL"
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -122,6 +122,9 @@ A hammer icon should appear in the chat window, indicating that your GrowthBook
|
|
|
122
122
|
- `get_experiment`: Fetch details for a specific experiment by ID.
|
|
123
123
|
- `get_attributes`: List all user attributes tracked in GrowthBook (useful for targeting).
|
|
124
124
|
- `create_experiment`: Creates a feature-flag based experiment.
|
|
125
|
+
- `get_defaults`: Get default values for experiments including hypothesis, description, datasource, and assignment query. (Runs automatically when the create experiment tool is called.)
|
|
126
|
+
- `create_defaults`: Set custom default values for experiments that will be used when creating new experiments.
|
|
127
|
+
- `clear_user_defaults`: Clear user-defined defaults and revert to automatic defaults.
|
|
125
128
|
|
|
126
129
|
- **Environments**
|
|
127
130
|
|
package/build/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { registerDefaultsTools } from "./tools/defaults.js";
|
|
|
12
12
|
export const baseApiUrl = getApiUrl();
|
|
13
13
|
export const apiKey = getApiKey();
|
|
14
14
|
export const appOrigin = getAppOrigin();
|
|
15
|
-
export const user = getUser();
|
|
15
|
+
export const user = await getUser(baseApiUrl, apiKey);
|
|
16
16
|
// Create an MCP server
|
|
17
17
|
const server = new McpServer({
|
|
18
18
|
name: "GrowthBook MCP",
|
package/build/tools/defaults.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { handleResNotOk } from "../utils.js";
|
|
2
2
|
import envPaths from "env-paths";
|
|
3
|
-
import { writeFile, readFile } from "fs/promises";
|
|
3
|
+
import { writeFile, readFile, mkdir, unlink } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
const paths = envPaths("growthbook-mcp"); // Use your app name
|
|
6
7
|
const experimentDefaultsDir = paths.config; // This is the recommended config directory
|
|
7
8
|
const experimentDefaultsFile = join(experimentDefaultsDir, "experiment-defaults.json");
|
|
9
|
+
const userDefaultsFile = join(experimentDefaultsDir, "user-defaults.json");
|
|
8
10
|
export async function createDefaults(apiKey, baseApiUrl) {
|
|
9
11
|
try {
|
|
10
12
|
const experimentsResponse = await fetch(`${baseApiUrl}/api/v1/experiments`, {
|
|
@@ -42,6 +44,10 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
42
44
|
datasource: "",
|
|
43
45
|
assignmentQuery,
|
|
44
46
|
environments,
|
|
47
|
+
filePaths: {
|
|
48
|
+
experimentDefaultsFile,
|
|
49
|
+
userDefaultsFile,
|
|
50
|
+
},
|
|
45
51
|
timestamp: new Date().toISOString(),
|
|
46
52
|
};
|
|
47
53
|
}
|
|
@@ -121,6 +127,10 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
121
127
|
datasource: mostFrequentDS.ds,
|
|
122
128
|
assignmentQuery: mostFrequentDS.aq,
|
|
123
129
|
environments,
|
|
130
|
+
filePaths: {
|
|
131
|
+
experimentDefaultsFile,
|
|
132
|
+
userDefaultsFile,
|
|
133
|
+
},
|
|
124
134
|
timestamp: new Date().toISOString(),
|
|
125
135
|
};
|
|
126
136
|
}
|
|
@@ -128,16 +138,94 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
128
138
|
throw error;
|
|
129
139
|
}
|
|
130
140
|
}
|
|
141
|
+
async function getUserDefaults() {
|
|
142
|
+
try {
|
|
143
|
+
const userDefaultsData = await readFile(userDefaultsFile, "utf8");
|
|
144
|
+
return JSON.parse(userDefaultsData);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (error.code === "ENOENT") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
131
153
|
export async function getDefaults(apiKey, baseApiUrl) {
|
|
154
|
+
// First check for user-defined defaults
|
|
155
|
+
const userDefaults = await getUserDefaults();
|
|
156
|
+
if (userDefaults) {
|
|
157
|
+
// User has set defaults, use them for datasource/assignment/environments
|
|
158
|
+
// But still get the automatic defaults for names/hypotheses/descriptions
|
|
159
|
+
let autoDefaults = {
|
|
160
|
+
name: [],
|
|
161
|
+
hypothesis: [],
|
|
162
|
+
description: [],
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
// Try to get existing auto-generated defaults for name/hypothesis/description
|
|
166
|
+
const experimentDefaultsData = await readFile(experimentDefaultsFile, "utf8");
|
|
167
|
+
const parsedExperimentDefaults = JSON.parse(experimentDefaultsData);
|
|
168
|
+
if (parsedExperimentDefaults &&
|
|
169
|
+
new Date(parsedExperimentDefaults.timestamp).getTime() >
|
|
170
|
+
new Date().getTime() - 1000 * 60 * 60 * 24 * 30 // 30 days
|
|
171
|
+
) {
|
|
172
|
+
autoDefaults = {
|
|
173
|
+
name: parsedExperimentDefaults.name || [],
|
|
174
|
+
hypothesis: parsedExperimentDefaults.hypothesis || [],
|
|
175
|
+
description: parsedExperimentDefaults.description || [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Re-generate auto defaults if expired
|
|
180
|
+
const generatedDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
181
|
+
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
182
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults, null, 2));
|
|
183
|
+
autoDefaults = {
|
|
184
|
+
name: generatedDefaults.name,
|
|
185
|
+
hypothesis: generatedDefaults.hypothesis,
|
|
186
|
+
description: generatedDefaults.description,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error.code === "ENOENT") {
|
|
192
|
+
// Generate new auto defaults
|
|
193
|
+
const generatedDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
194
|
+
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
195
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults, null, 2));
|
|
196
|
+
autoDefaults = {
|
|
197
|
+
name: generatedDefaults.name,
|
|
198
|
+
hypothesis: generatedDefaults.hypothesis,
|
|
199
|
+
description: generatedDefaults.description,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Combine user defaults with auto defaults
|
|
204
|
+
return {
|
|
205
|
+
name: autoDefaults.name || [],
|
|
206
|
+
hypothesis: autoDefaults.hypothesis || [],
|
|
207
|
+
description: autoDefaults.description || [],
|
|
208
|
+
datasource: userDefaults.datasourceId,
|
|
209
|
+
assignmentQuery: userDefaults.assignmentQueryId,
|
|
210
|
+
environments: userDefaults.environments,
|
|
211
|
+
filePaths: {
|
|
212
|
+
experimentDefaultsFile,
|
|
213
|
+
userDefaultsFile,
|
|
214
|
+
},
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// No user defaults, use fully automatic defaults
|
|
132
219
|
let experimentDefaults;
|
|
133
220
|
try {
|
|
134
221
|
const experimentDefaultsData = await readFile(experimentDefaultsFile, "utf8");
|
|
135
222
|
let parsedExperimentDefaults = JSON.parse(experimentDefaultsData);
|
|
136
223
|
if (!parsedExperimentDefaults ||
|
|
137
|
-
parsedExperimentDefaults.timestamp <
|
|
224
|
+
new Date(parsedExperimentDefaults.timestamp).getTime() <
|
|
138
225
|
new Date().getTime() - 1000 * 60 * 60 * 24 * 30 // 30 days
|
|
139
226
|
) {
|
|
140
227
|
const generatedExperimentDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
228
|
+
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
141
229
|
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults, null, 2));
|
|
142
230
|
parsedExperimentDefaults = generatedExperimentDefaults;
|
|
143
231
|
}
|
|
@@ -147,6 +235,7 @@ export async function getDefaults(apiKey, baseApiUrl) {
|
|
|
147
235
|
if (error.code === "ENOENT") {
|
|
148
236
|
// experimentDefaultsFile does not exist, generate new defaults
|
|
149
237
|
const generatedExperimentDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
238
|
+
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
150
239
|
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults, null, 2));
|
|
151
240
|
experimentDefaults = generatedExperimentDefaults;
|
|
152
241
|
}
|
|
@@ -163,7 +252,70 @@ export async function registerDefaultsTools({ server, baseApiUrl, apiKey, }) {
|
|
|
163
252
|
server.tool("get_defaults", "Get the default values for experiments, including hypothesis, description, datasource, assignment query, and environments.", {}, async () => {
|
|
164
253
|
const defaults = await getDefaults(apiKey, baseApiUrl);
|
|
165
254
|
return {
|
|
166
|
-
content: [
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: JSON.stringify(defaults, null, 2),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
167
261
|
};
|
|
168
262
|
});
|
|
263
|
+
server.tool("set_user_defaults", "Set user-defined defaults for datasource, assignment query, and environments. These will override the automatic defaults for these fields.", {
|
|
264
|
+
datasourceId: z.string().describe("The data source ID to use as default"),
|
|
265
|
+
assignmentQueryId: z
|
|
266
|
+
.string()
|
|
267
|
+
.describe("The assignment query ID to use as default"),
|
|
268
|
+
environments: z
|
|
269
|
+
.array(z.string())
|
|
270
|
+
.describe("List of environment IDs to use as defaults"),
|
|
271
|
+
}, async ({ datasourceId, assignmentQueryId, environments }) => {
|
|
272
|
+
try {
|
|
273
|
+
const userDefaults = {
|
|
274
|
+
datasourceId,
|
|
275
|
+
assignmentQueryId,
|
|
276
|
+
environments,
|
|
277
|
+
timestamp: new Date().toISOString(),
|
|
278
|
+
};
|
|
279
|
+
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
280
|
+
await writeFile(userDefaultsFile, JSON.stringify(userDefaults, null, 2));
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: `User defaults have been saved:\n\n${JSON.stringify(userDefaults, null, 2)} to ${userDefaultsFile}\n\nThese will be used when creating new experiments.`,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
throw new Error(`Error setting user defaults: ${error}`);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
server.tool("clear_user_defaults", "Clear user-defined defaults and revert to automatic defaults.", {}, async () => {
|
|
295
|
+
try {
|
|
296
|
+
await readFile(userDefaultsFile, "utf8");
|
|
297
|
+
await unlink(userDefaultsFile);
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: "User defaults have been cleared. The system will now use automatic defaults.",
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error.code === "ENOENT") {
|
|
309
|
+
return {
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
type: "text",
|
|
313
|
+
text: "No user defaults were set.",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
throw new Error(`Error clearing user defaults: ${error}`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
169
321
|
}
|
|
@@ -57,7 +57,7 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
57
57
|
// Loop through the environments and create a rule for each one keyed by environment name
|
|
58
58
|
environments: defaultEnvironments.reduce((acc, env) => {
|
|
59
59
|
acc[env] = {
|
|
60
|
-
enabled:
|
|
60
|
+
enabled: false,
|
|
61
61
|
rules: [
|
|
62
62
|
{
|
|
63
63
|
type: "force",
|
|
@@ -176,7 +176,12 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
176
176
|
.string()
|
|
177
177
|
.describe("Variation name. Base name off the examples from get_defaults. If none are available, use a short, descriptive name that captures the essence of the variation."),
|
|
178
178
|
value: z
|
|
179
|
-
.union([
|
|
179
|
+
.union([
|
|
180
|
+
z.string(),
|
|
181
|
+
z.number(),
|
|
182
|
+
z.boolean(),
|
|
183
|
+
z.record(z.string(), z.any()),
|
|
184
|
+
])
|
|
180
185
|
.describe("The value of the control and each of the variations. The value should be a string, number, boolean, or object. If it's an object, it should be a valid JSON object."),
|
|
181
186
|
}))
|
|
182
187
|
.describe("Experiment variations. The key should be the variation name and the value should be the variation value. Look to variations included in preview experiments for guidance on generation. The default or control variation should always be first."),
|
|
@@ -203,6 +208,7 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
203
208
|
name,
|
|
204
209
|
description,
|
|
205
210
|
hypothesis,
|
|
211
|
+
owner: user.email,
|
|
206
212
|
trackingKey: name.toLowerCase().replace(/[^a-z0-9]/g, "-"),
|
|
207
213
|
tags: ["mcp"],
|
|
208
214
|
assignmentQueryId: experimentDefaults?.assignmentQuery,
|
|
@@ -226,7 +232,7 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
226
232
|
const flagId = `flag_${name.toLowerCase().replace(/[^a-z0-9]/g, "_")}`;
|
|
227
233
|
const flagPayload = {
|
|
228
234
|
id: flagId,
|
|
229
|
-
owner: user,
|
|
235
|
+
owner: user.name,
|
|
230
236
|
defaultValue: variations[0].value,
|
|
231
237
|
valueType: typeof variations[0].value === "string"
|
|
232
238
|
? "string"
|
|
@@ -237,7 +243,7 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
237
243
|
environments: {
|
|
238
244
|
...experimentDefaults.environments.reduce((acc, env) => {
|
|
239
245
|
acc[env] = {
|
|
240
|
-
enabled:
|
|
246
|
+
enabled: false,
|
|
241
247
|
rules: [
|
|
242
248
|
{
|
|
243
249
|
type: "experiment-ref",
|
|
@@ -261,8 +267,8 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
261
267
|
},
|
|
262
268
|
body: JSON.stringify(flagPayload),
|
|
263
269
|
});
|
|
264
|
-
const flagData = await flagRes.json();
|
|
265
270
|
await handleResNotOk(flagRes);
|
|
271
|
+
const flagData = await flagRes.json();
|
|
266
272
|
const experimentLink = generateLinkToGrowthBook(appOrigin, "experiment", experimentData.experiment.id);
|
|
267
273
|
const flagLink = generateLinkToGrowthBook(appOrigin, "features", flagData.feature.id);
|
|
268
274
|
const { stub, docs, language } = getDocsMetadata(fileExtension);
|
package/build/tools/features.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getDocsMetadata, handleResNotOk, generateLinkToGrowthBook, SUPPORTED_FILE_EXTENSIONS, } from "../utils.js";
|
|
3
3
|
import { exec } from "child_process";
|
|
4
|
+
import { getDefaults } from "./defaults.js";
|
|
4
5
|
export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, user, }) {
|
|
5
6
|
/**
|
|
6
7
|
* Tool: create_feature_flag
|
|
@@ -14,7 +15,7 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
14
15
|
.string()
|
|
15
16
|
.optional()
|
|
16
17
|
.default("")
|
|
17
|
-
.describe("A
|
|
18
|
+
.describe("A brief description of the feature flag"),
|
|
18
19
|
valueType: z
|
|
19
20
|
.enum(["string", "number", "boolean", "json"])
|
|
20
21
|
.describe("The value type the feature flag will return"),
|
|
@@ -25,13 +26,37 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
25
26
|
.enum(SUPPORTED_FILE_EXTENSIONS)
|
|
26
27
|
.describe("The extension of the current file. If it's unclear, ask the user."),
|
|
27
28
|
}, async ({ id, description, valueType, defaultValue, fileExtension }) => {
|
|
29
|
+
// get environments
|
|
30
|
+
let environments = [];
|
|
31
|
+
const defaults = await getDefaults(apiKey, baseApiUrl);
|
|
32
|
+
if (defaults.environments) {
|
|
33
|
+
environments = defaults.environments;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const envRes = await fetch(`${baseApiUrl}/api/v1/features/environments`, {
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Bearer ${apiKey}`,
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
await handleResNotOk(envRes);
|
|
43
|
+
const envData = await envRes.json();
|
|
44
|
+
environments = envData.environments.map((env) => env.id);
|
|
45
|
+
}
|
|
28
46
|
const payload = {
|
|
29
47
|
id,
|
|
30
48
|
description,
|
|
31
|
-
owner: user,
|
|
49
|
+
owner: user.name,
|
|
32
50
|
valueType,
|
|
33
51
|
defaultValue,
|
|
34
52
|
tags: ["mcp"],
|
|
53
|
+
environments: environments.reduce((acc, env) => {
|
|
54
|
+
acc[env] = {
|
|
55
|
+
enabled: false,
|
|
56
|
+
rules: [],
|
|
57
|
+
};
|
|
58
|
+
return acc;
|
|
59
|
+
}, {}),
|
|
35
60
|
};
|
|
36
61
|
try {
|
|
37
62
|
const res = await fetch(`${baseApiUrl}/api/v1/features`, {
|
package/build/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { getFeatureFlagDocs } from "./docs.js";
|
|
2
3
|
// Shared file extension enum for all MCP tools
|
|
3
4
|
export const SUPPORTED_FILE_EXTENSIONS = [
|
|
@@ -52,12 +53,39 @@ export function getAppOrigin() {
|
|
|
52
53
|
const userAppOrigin = process.env.GB_APP_ORIGIN;
|
|
53
54
|
return `${userAppOrigin || defaultAppOrigin}`;
|
|
54
55
|
}
|
|
55
|
-
export function getUser() {
|
|
56
|
-
const user = process.env.GB_USER;
|
|
56
|
+
export async function getUser(baseApiUrl, apiKey) {
|
|
57
|
+
const user = process.env.GB_EMAIL || process.env.GB_USER;
|
|
57
58
|
if (!user) {
|
|
58
|
-
throw new Error("
|
|
59
|
+
throw new Error("GB_EMAIL environment variable is required");
|
|
60
|
+
}
|
|
61
|
+
// Show deprecation warning if using the old variable
|
|
62
|
+
if (process.env.GB_USER && !process.env.GB_EMAIL) {
|
|
63
|
+
console.error("⚠️ GB_USER is deprecated. Use GB_EMAIL instead.");
|
|
64
|
+
}
|
|
65
|
+
const emailSchema = z.string().email();
|
|
66
|
+
if (!emailSchema.safeParse(user).success) {
|
|
67
|
+
throw new Error("GB_EMAIL is not a valid email");
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const users = await fetch(`${baseApiUrl}/api/v1/members?userEmail=${user}`, {
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: `Bearer ${apiKey}`,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
await handleResNotOk(users);
|
|
76
|
+
const usersData = await users.json();
|
|
77
|
+
if (usersData.members.length === 0) {
|
|
78
|
+
throw new Error(`Email not found in GrowthBook. Update GB_EMAIL environment variable to your email address in GrowthBook.`);
|
|
79
|
+
}
|
|
80
|
+
const userFromGrowthBook = {
|
|
81
|
+
email: usersData.members[0].email,
|
|
82
|
+
name: usersData.members[0].name,
|
|
83
|
+
};
|
|
84
|
+
return userFromGrowthBook;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Error fetching user from GrowthBook. Please check your GB_EMAIL and GB_API_KEY environment variables.`);
|
|
59
88
|
}
|
|
60
|
-
return user;
|
|
61
89
|
}
|
|
62
90
|
export function getDocsMetadata(extension) {
|
|
63
91
|
switch (extension) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthbook/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"access": "public",
|
|
6
6
|
"homepage": "https://github.com/growthbook/growthbook-mcp",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"packageManager": "pnpm@10.6.1",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.17.2",
|
|
26
26
|
"env-paths": "^3.0.0",
|
|
27
27
|
"zod": "^3.25.67"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^24.
|
|
31
|
-
"typescript": "^5.
|
|
30
|
+
"@types/node": "^24.2.1",
|
|
31
|
+
"typescript": "^5.9.2"
|
|
32
32
|
},
|
|
33
33
|
"type": "module"
|
|
34
34
|
}
|