@growthbook/mcp 0.1.2 → 0.1.3

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 CHANGED
@@ -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
 
@@ -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: [{ type: "text", text: JSON.stringify(defaults, null, 2) }],
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
  }
@@ -261,8 +261,8 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
261
261
  },
262
262
  body: JSON.stringify(flagPayload),
263
263
  });
264
- const flagData = await flagRes.json();
265
264
  await handleResNotOk(flagRes);
265
+ const flagData = await flagRes.json();
266
266
  const experimentLink = generateLinkToGrowthBook(appOrigin, "experiment", experimentData.experiment.id);
267
267
  const flagLink = generateLinkToGrowthBook(appOrigin, "features", flagData.feature.id);
268
268
  const { stub, docs, language } = getDocsMetadata(fileExtension);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthbook/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "",
5
5
  "access": "public",
6
6
  "homepage": "https://github.com/growthbook/growthbook-mcp",