@growthbook/mcp 1.7.0 → 1.8.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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthbook/mcp",
|
|
3
3
|
"mcpName": "io.github.growthbook/growthbook-mcp",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"description": "MCP Server for interacting with GrowthBook",
|
|
6
6
|
"access": "public",
|
|
7
7
|
"homepage": "https://github.com/growthbook/growthbook-mcp",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"author": "GrowthBook",
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
42
42
|
"env-paths": "^4.0.0",
|
|
43
43
|
"zod": "^4.3.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@types/node": "^25.
|
|
46
|
+
"@types/node": "^25.3.5",
|
|
47
47
|
"@vitest/coverage-v8": "^4.0.18",
|
|
48
48
|
"typescript": "^5.9.2",
|
|
49
49
|
"vitest": "^4.0.18"
|
|
@@ -425,25 +425,6 @@ export function formatDefaults(defaults) {
|
|
|
425
425
|
return parts.join("\n");
|
|
426
426
|
}
|
|
427
427
|
// ─── Stale Features ─────────────────────────────────────────────────
|
|
428
|
-
// Common SDK patterns to search for when removing a flag from the codebase
|
|
429
|
-
const SDK_PATTERNS = [
|
|
430
|
-
// JS/TS/React
|
|
431
|
-
"isOn",
|
|
432
|
-
"getFeatureValue",
|
|
433
|
-
"useFeatureIsOn",
|
|
434
|
-
"useFeatureValue",
|
|
435
|
-
"evalFeature",
|
|
436
|
-
// Python
|
|
437
|
-
"is_on",
|
|
438
|
-
"get_feature_value",
|
|
439
|
-
// Go / Ruby / other
|
|
440
|
-
"IsOn",
|
|
441
|
-
"GetFeatureValue",
|
|
442
|
-
"feature_is_on",
|
|
443
|
-
];
|
|
444
|
-
function buildSearchPatterns(flagId) {
|
|
445
|
-
return SDK_PATTERNS.map((fn) => `${fn}("${flagId}")`).join(", ");
|
|
446
|
-
}
|
|
447
428
|
export function formatStaleFeatureFlags(data, requestedIds) {
|
|
448
429
|
const features = data.features || {};
|
|
449
430
|
const foundIds = Object.keys(features);
|
|
@@ -509,7 +490,7 @@ export function formatStaleFeatureFlags(data, requestedIds) {
|
|
|
509
490
|
parts.push(`- **\`${f.featureId}\`**: STALE (${f.staleReason}) — needs manual review`);
|
|
510
491
|
}
|
|
511
492
|
parts.push(` ${envNote}`);
|
|
512
|
-
parts.push(` Search for
|
|
493
|
+
parts.push(` Search for \`${id}\` in relevant source files to find usages.`);
|
|
513
494
|
parts.push("");
|
|
514
495
|
}
|
|
515
496
|
// Summary
|
|
@@ -242,12 +242,16 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
242
242
|
confirmedDefaultsReviewed: z
|
|
243
243
|
.boolean()
|
|
244
244
|
.describe("Set to true to confirm you have called get_defaults and reviewed the output to guide these parameters."),
|
|
245
|
+
customFields: z
|
|
246
|
+
.record(z.string(), z.string())
|
|
247
|
+
.optional()
|
|
248
|
+
.describe("Custom field values as key-value pairs. Keys are custom field IDs, values are string representations (e.g. {\"priority\": \"high\", \"team\": \"growth\"})."),
|
|
245
249
|
}),
|
|
246
250
|
annotations: {
|
|
247
251
|
readOnlyHint: false,
|
|
248
252
|
destructiveHint: false,
|
|
249
253
|
},
|
|
250
|
-
}, async ({ description, hypothesis, name, valueType, variations, fileExtension, confirmedDefaultsReviewed, project, featureId, }) => {
|
|
254
|
+
}, async ({ description, hypothesis, name, valueType, variations, fileExtension, confirmedDefaultsReviewed, project, featureId, customFields, }) => {
|
|
251
255
|
if (!confirmedDefaultsReviewed) {
|
|
252
256
|
return {
|
|
253
257
|
content: [
|
|
@@ -275,6 +279,7 @@ export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin,
|
|
|
275
279
|
name: variation.name,
|
|
276
280
|
})),
|
|
277
281
|
...(project && { project }),
|
|
282
|
+
...(customFields && { customFields }),
|
|
278
283
|
};
|
|
279
284
|
try {
|
|
280
285
|
const experimentRes = await fetchWithRateLimit(`${baseApiUrl}/api/v1/experiments`, {
|
package/server/tools/features.js
CHANGED
|
@@ -15,7 +15,7 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
15
15
|
readOnlyHint: false,
|
|
16
16
|
destructiveHint: false,
|
|
17
17
|
},
|
|
18
|
-
}, async ({ id, valueType, defaultValue, description, project, fileExtension, }) => {
|
|
18
|
+
}, async ({ id, valueType, defaultValue, description, project, fileExtension, customFields, }) => {
|
|
19
19
|
// get environments
|
|
20
20
|
let environments = [];
|
|
21
21
|
const defaults = await getDefaults(apiKey, baseApiUrl);
|
|
@@ -45,6 +45,7 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
45
45
|
return acc;
|
|
46
46
|
}, {}),
|
|
47
47
|
...(project && { project }),
|
|
48
|
+
...(customFields && { customFields }),
|
|
48
49
|
};
|
|
49
50
|
try {
|
|
50
51
|
const res = await fetchWithRateLimit(`${baseApiUrl}/api/v1/features`, {
|
|
@@ -192,12 +193,12 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
192
193
|
*/
|
|
193
194
|
server.registerTool("get_stale_feature_flags", {
|
|
194
195
|
title: "Get Stale Feature Flags",
|
|
195
|
-
description: "Given a list of feature flag IDs, checks whether each one is stale and returns cleanup guidance including replacement values and SDK search patterns. You MUST provide featureIds — gather them first from the user, from the current file context, or by
|
|
196
|
+
description: "Given a list of feature flag IDs, checks whether each one is stale and returns cleanup guidance including replacement values and SDK search patterns. You MUST provide featureIds — gather them first from the user, from the current file context, or by using the get_feature_flags tool to list all flags and then searching the codebase for those flag IDs to determine which are present.",
|
|
196
197
|
inputSchema: z.object({
|
|
197
198
|
featureIds: z
|
|
198
199
|
.array(z.string())
|
|
199
200
|
.optional()
|
|
200
|
-
.describe("REQUIRED. One or more feature flag IDs to check (e.g. [\"my-feature\", \"dark-mode\"]). Gather IDs first from the user, from code context, or by
|
|
201
|
+
.describe("REQUIRED. One or more feature flag IDs to check (e.g. [\"my-feature\", \"dark-mode\"]). Gather IDs first from the user, from code context, or by using get_feature_flags to list flags and searching the codebase for those IDs."),
|
|
201
202
|
}),
|
|
202
203
|
annotations: {
|
|
203
204
|
readOnlyHint: true,
|
|
@@ -215,8 +216,7 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
|
|
|
215
216
|
"To gather feature flag IDs, try one of these approaches:",
|
|
216
217
|
"1. **Ask the user** which flags they want to check",
|
|
217
218
|
"2. **Extract from current file context** — look for flag IDs in the open file",
|
|
218
|
-
"3. **
|
|
219
|
-
' `grep -rn "isOn\\|getFeatureValue\\|useFeatureIsOn\\|useFeatureValue\\|evalFeature" --include="*.{ts,tsx,js,jsx,py,go,rb}"`',
|
|
219
|
+
"3. **Use the `get_feature_flags` tool** to list all flags, then search the codebase for those flag IDs to determine which are present",
|
|
220
220
|
"",
|
|
221
221
|
"Then call this tool again with the discovered flag IDs.",
|
|
222
222
|
].join("\n"),
|
package/server/utils.js
CHANGED
|
@@ -405,6 +405,10 @@ export const featureFlagSchema = {
|
|
|
405
405
|
fileExtension: z
|
|
406
406
|
.enum(SUPPORTED_FILE_EXTENSIONS)
|
|
407
407
|
.describe("The extension of the current file. If it's unclear, ask the user."),
|
|
408
|
+
customFields: z
|
|
409
|
+
.record(z.string(), z.string())
|
|
410
|
+
.optional()
|
|
411
|
+
.describe("Custom field values as key-value pairs. Keys are custom field IDs, values are string representations (e.g. {\"priority\": \"high\", \"team\": \"growth\"})."),
|
|
408
412
|
};
|
|
409
413
|
function sleep(ms) {
|
|
410
414
|
return new Promise((resolve) => setTimeout(resolve, ms));
|