@growthbook/mcp 0.1.0 → 0.1.2

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.
@@ -1,65 +1,37 @@
1
1
  import { z } from "zod";
2
- import { getDocsMetadata, findImplementationDocs, handleResNotOk, generateLinkToGrowthBook, } from "../utils.js";
2
+ import { getDocsMetadata, handleResNotOk, generateLinkToGrowthBook, SUPPORTED_FILE_EXTENSIONS, } from "../utils.js";
3
+ import { exec } from "child_process";
3
4
  export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, user, }) {
4
5
  /**
5
6
  * Tool: create_feature_flag
6
- * Description: Creates, adds, or wraps an element with a feature flag in GrowthBook. Allows specifying key, type, default value, and other metadata.
7
7
  */
8
- server.tool("create_feature_flag", "Create, add, or wrap an element with a feature flag.", {
8
+ server.tool("create_feature_flag", "Creates a new feature flag in GrowthBook and modifies the codebase when relevant.", {
9
9
  id: z
10
10
  .string()
11
11
  .regex(/^[a-zA-Z0-9_-]+$/, "Feature key can only include letters, numbers, hyphens, and underscores.")
12
12
  .describe("A unique key name for the feature"),
13
- archived: z
14
- .boolean()
15
- .optional()
16
- .default(false)
17
- .describe("Whether the feature flag is archived"),
18
13
  description: z
19
14
  .string()
20
15
  .optional()
21
16
  .default("")
22
- .describe("A description of the feature flag"),
23
- project: z
24
- .string()
25
- .optional()
26
- .default("")
27
- .describe("The project the feature flag belongs to"),
17
+ .describe("A briefdescription of the feature flag"),
28
18
  valueType: z
29
19
  .enum(["string", "number", "boolean", "json"])
30
20
  .describe("The value type the feature flag will return"),
31
21
  defaultValue: z
32
22
  .string()
33
23
  .describe("The default value of the feature flag"),
34
- tags: z
35
- .array(z.string())
36
- .optional()
37
- .describe("Tags for the feature flag"),
38
24
  fileExtension: z
39
- .enum([
40
- ".tsx",
41
- ".jsx",
42
- ".ts",
43
- ".js",
44
- ".vue",
45
- ".py",
46
- ".go",
47
- ".php",
48
- ".rb",
49
- ".java",
50
- ".cs",
51
- ])
25
+ .enum(SUPPORTED_FILE_EXTENSIONS)
52
26
  .describe("The extension of the current file. If it's unclear, ask the user."),
53
- }, async ({ id, archived, description, project, valueType, defaultValue, tags, fileExtension, }) => {
27
+ }, async ({ id, description, valueType, defaultValue, fileExtension }) => {
54
28
  const payload = {
55
29
  id,
56
- archived,
57
30
  description,
58
31
  owner: user,
59
- project,
60
32
  valueType,
61
33
  defaultValue,
62
- tags,
34
+ tags: ["mcp"],
63
35
  };
64
36
  try {
65
37
  const res = await fetch(`${baseApiUrl}/api/v1/features`, {
@@ -70,23 +42,9 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
70
42
  },
71
43
  body: JSON.stringify(payload),
72
44
  });
73
- if (!res.ok) {
74
- let errorMessage = `HTTP ${res.status} ${res.statusText}`;
75
- try {
76
- const errorBody = await res.json();
77
- errorMessage += `: ${JSON.stringify(errorBody)}`;
78
- }
79
- catch {
80
- // fallback to text if not JSON
81
- const errorText = await res.text();
82
- if (errorText)
83
- errorMessage += `: ${errorText}`;
84
- }
85
- throw new Error(errorMessage);
86
- }
45
+ await handleResNotOk(res);
87
46
  const data = await res.json();
88
- const docsText = await findImplementationDocs(fileExtension);
89
- const { docs, language } = getDocsMetadata(fileExtension);
47
+ const { docs, language, stub } = getDocsMetadata(fileExtension);
90
48
  const linkToGrowthBook = generateLinkToGrowthBook(appOrigin, "features", id);
91
49
  const text = `This is the API response: ${JSON.stringify(data, null, 2)}
92
50
 
@@ -98,7 +56,7 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
98
56
  **How to use it in your code:**
99
57
  (To the LLM agent. Use the text below to properly add the feature flag to the codebase. Also, surface the relevant docs/implemenation to the user)
100
58
 
101
- ${docsText}
59
+ ${stub}
102
60
 
103
61
  **Want to learn more?**
104
62
  See the [GrowthBook ${language} docs](${docs})
@@ -108,26 +66,21 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
108
66
  };
109
67
  }
110
68
  catch (error) {
111
- console.error("Error creating feature flag:", error);
112
- throw error;
69
+ throw new Error(`Error creating feature flag: ${error}`);
113
70
  }
114
71
  });
115
72
  /**
116
73
  * Tool: get_feature_flags
117
- * Description: Fetches all feature flags from the GrowthBook API, with optional limit, offset, and project filtering.
118
74
  */
119
- server.tool("get_feature_flags", "Fetches all feature flags from the GrowthBook API. Flags are returned in the order they were created, from oldest to newest.", {
75
+ server.tool("get_feature_flags", "Fetches all feature flags from the GrowthBook API, with optional limit, offset, and project filtering.", {
120
76
  limit: z.number().optional().default(100),
121
77
  offset: z.number().optional().default(0),
122
- project: z.string().optional(),
123
- }, async ({ limit, offset, project }) => {
78
+ }, async ({ limit, offset }) => {
124
79
  try {
125
80
  const queryParams = new URLSearchParams({
126
81
  limit: limit?.toString(),
127
82
  offset: offset?.toString(),
128
83
  });
129
- if (project)
130
- queryParams.append("project", project);
131
84
  const res = await fetch(`${baseApiUrl}/api/v1/features?${queryParams.toString()}`, {
132
85
  headers: {
133
86
  Authorization: `Bearer ${apiKey}`,
@@ -141,13 +94,11 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
141
94
  };
142
95
  }
143
96
  catch (error) {
144
- console.error("Error fetching flags:", error);
145
- throw error;
97
+ throw new Error(`Error fetching flags: ${error}`);
146
98
  }
147
99
  });
148
100
  /**
149
101
  * Tool: get_single_feature_flag
150
- * Description: Fetches a specific feature flag from the GrowthBook API by its ID, with optional project filtering.
151
102
  */
152
103
  server.tool("get_single_feature_flag", "Fetches a specific feature flag from the GrowthBook API", {
153
104
  id: z.string().describe("The ID of the feature flag"),
@@ -180,26 +131,21 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
180
131
  };
181
132
  }
182
133
  catch (error) {
183
- console.error("Error fetching flags:", error);
184
- throw error;
134
+ throw new Error(`Error fetching flags: ${error}`);
185
135
  }
186
136
  });
187
137
  /**
188
138
  * Tool: get_stale_safe_rollouts
189
- * Description: Fetches all complete safe rollouts (rolled-back or released) from the GrowthBook API, with optional limit, offset, and project filtering.
190
139
  */
191
140
  server.tool("get_stale_safe_rollouts", "Fetches all complete safe rollouts (rolled-back or released) from the GrowthBook API", {
192
141
  limit: z.number().optional().default(100),
193
142
  offset: z.number().optional().default(0),
194
- project: z.string().optional(),
195
- }, async ({ limit, offset, project }) => {
143
+ }, async ({ limit, offset }) => {
196
144
  try {
197
145
  const queryParams = new URLSearchParams({
198
146
  limit: limit?.toString(),
199
147
  offset: offset?.toString(),
200
148
  });
201
- if (project)
202
- queryParams.append("project", project);
203
149
  const res = await fetch(`${baseApiUrl}/api/v1/features?${queryParams.toString()}`, {
204
150
  headers: {
205
151
  Authorization: `Bearer ${apiKey}`,
@@ -236,25 +182,45 @@ export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, us
236
182
  };
237
183
  }
238
184
  catch (error) {
239
- console.error("Error fetching stale safe rollouts:", error);
240
- throw error;
185
+ throw new Error(`Error fetching stale safe rollouts: ${error}`);
241
186
  }
242
187
  });
243
188
  /**
244
189
  * Tool: generate_flag_types
245
- * Description: Generates types for feature flags using the GrowthBook CLI.
246
190
  */
247
- server.tool("generate_flag_types", "Generate types for feature flags", {}, async () => {
248
- const text = `Run the following commands for the user to generate types for their feature flags:
249
-
250
- The first command will log you in to GrowthBook:
251
- npx -y growthbook@latest auth login -k ${apiKey} -u ${baseApiUrl} -p default
252
-
253
- The second command will generate types for your feature flags:
254
- npx -y growthbook@latest features generate-types -u ${baseApiUrl}
255
- `;
256
- return {
257
- content: [{ type: "text", text }],
258
- };
191
+ server.tool("generate_flag_types", "Generate types for feature flags", {
192
+ currentWorkingDirectory: z
193
+ .string()
194
+ .describe("The current working directory of the user's project"),
195
+ }, async ({ currentWorkingDirectory }) => {
196
+ function runCommand(command, cwd) {
197
+ return new Promise((resolve, reject) => {
198
+ exec(command, { cwd }, (error, stdout, stderr) => {
199
+ if (error) {
200
+ reject(stderr || error.message);
201
+ }
202
+ else {
203
+ resolve(stdout);
204
+ }
205
+ });
206
+ });
207
+ }
208
+ try {
209
+ // Login command
210
+ await runCommand(`npx -y growthbook@latest auth login -k ${apiKey} -u ${baseApiUrl} -p default`, currentWorkingDirectory);
211
+ // Generate types command
212
+ const output = await runCommand(`npx -y growthbook@latest features generate-types -u ${baseApiUrl}`, currentWorkingDirectory);
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: `✅ Types generated successfully:\n${output}`,
218
+ },
219
+ ],
220
+ };
221
+ }
222
+ catch (error) {
223
+ throw new Error(`Error generating types: ${error}`);
224
+ }
259
225
  });
260
226
  }
@@ -2,11 +2,10 @@ import { z } from "zod";
2
2
  import { handleResNotOk } from "../utils.js";
3
3
  /**
4
4
  * Tool: get_projects
5
- * Description: Fetches all projects from the GrowthBook API, with optional limit and offset for pagination.
6
5
  */
7
6
  export function registerProjectTools({ server, baseApiUrl, apiKey, }) {
8
7
  server.tool("get_projects", "Fetches all projects from the GrowthBook API", {
9
- limit: z.number().optional().default(10),
8
+ limit: z.number().optional().default(100),
10
9
  offset: z.number().optional().default(0),
11
10
  }, async ({ limit, offset }) => {
12
11
  const queryParams = new URLSearchParams({
@@ -3,23 +3,16 @@ import { handleResNotOk } from "../utils.js";
3
3
  export function registerSdkConnectionTools({ server, baseApiUrl, apiKey, }) {
4
4
  /**
5
5
  * Tool: get_sdk_connections
6
- * Description: Retrieves all SDK connections, which are how GrowthBook connects to an app.
7
- * Users need the key, which is a public key that allows the app to fetch features and experiments from the API.
8
6
  */
9
- server.tool("get_sdk_connections", `Get all SDK connections,
10
- which are how GrowthBook connects to an app.
11
- Importantly, users need the key, which is a public client key that allows the app to fetch features and experiments the API `, {
7
+ server.tool("get_sdk_connections", "Get all SDK connections. SDK connections are how GrowthBook connects to an app. Users need the client key to fetch features and experiments from the API.", {
12
8
  limit: z.number().optional().default(100),
13
9
  offset: z.number().optional().default(0),
14
- project: z.string().optional(),
15
- }, async ({ limit, offset, project }) => {
10
+ }, async ({ limit, offset }) => {
16
11
  try {
17
12
  const queryParams = new URLSearchParams({
18
13
  limit: limit?.toString(),
19
14
  offset: offset?.toString(),
20
15
  });
21
- if (project)
22
- queryParams.append("project", project);
23
16
  const res = await fetch(`${baseApiUrl}/api/v1/sdk-connections?${queryParams.toString()}`, {
24
17
  headers: {
25
18
  Authorization: `Bearer ${apiKey}`,
@@ -33,15 +26,11 @@ export function registerSdkConnectionTools({ server, baseApiUrl, apiKey, }) {
33
26
  };
34
27
  }
35
28
  catch (error) {
36
- return {
37
- content: [{ type: "text", text: `Error: ${error}` }],
38
- };
29
+ throw new Error(`Error fetching sdk connections: ${error}`);
39
30
  }
40
31
  });
41
32
  /**
42
33
  * Tool: create_sdk_connection
43
- * Description: Creates an SDK connection for a user. Returns an SDK clientKey that can be used to fetch features and experiments.
44
- * Requires a name, language, and optionally an environment.
45
34
  */
46
35
  server.tool("create_sdk_connection", `Create an SDK connection for a user. Returns an SDK clientKey that can be used to fetch features and experiments.`, {
47
36
  name: z
@@ -123,9 +112,7 @@ export function registerSdkConnectionTools({ server, baseApiUrl, apiKey, }) {
123
112
  };
124
113
  }
125
114
  catch (error) {
126
- return {
127
- content: [{ type: "text", text: `Error: ${error}` }],
128
- };
115
+ throw new Error(`Error creating sdk connection: ${error}`);
129
116
  }
130
117
  });
131
118
  }
@@ -2,9 +2,8 @@ import { z } from "zod";
2
2
  import { searchGrowthBookDocs } from "../utils.js";
3
3
  /**
4
4
  * Tool: search_growthbook_docs
5
- * Description: Searches the GrowthBook documentation for information on how to use a feature, based on a user-provided query.
6
5
  */
7
- export function registerSearchTool({ server }) {
6
+ export function registerSearchTools({ server }) {
8
7
  server.tool("search_growthbook_docs", "Search the GrowthBook docs on how to use a feature", {
9
8
  query: z
10
9
  .string()
package/build/utils.js CHANGED
@@ -1,13 +1,34 @@
1
+ import { getFeatureFlagDocs } from "./docs.js";
2
+ // Shared file extension enum for all MCP tools
3
+ export const SUPPORTED_FILE_EXTENSIONS = [
4
+ ".tsx",
5
+ ".jsx",
6
+ ".ts",
7
+ ".js",
8
+ ".vue",
9
+ ".py",
10
+ ".go",
11
+ ".php",
12
+ ".rb",
13
+ ".java",
14
+ ".cs",
15
+ ".swift",
16
+ ".ex",
17
+ ".exs",
18
+ ".kt",
19
+ ".kts",
20
+ ".ktm",
21
+ ".dart",
22
+ ];
1
23
  export async function handleResNotOk(res) {
2
24
  if (!res.ok) {
25
+ const errorText = await res.text();
3
26
  let errorMessage = `HTTP ${res.status} ${res.statusText}`;
4
27
  try {
5
- const errorBody = await res.json();
28
+ const errorBody = JSON.parse(errorText);
6
29
  errorMessage += `: ${JSON.stringify(errorBody)}`;
7
30
  }
8
31
  catch {
9
- // fallback to text if not JSON
10
- const errorText = await res.text();
11
32
  if (errorText)
12
33
  errorMessage += `: ${errorText}`;
13
34
  }
@@ -44,69 +65,69 @@ export function getDocsMetadata(extension) {
44
65
  case ".jsx":
45
66
  return {
46
67
  language: "react",
47
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/react.mdx",
68
+ stub: getFeatureFlagDocs("react"),
48
69
  docs: "https://docs.growthbook.io/lib/react",
49
70
  };
50
71
  case ".ts":
51
72
  case ".js":
52
73
  return {
53
74
  language: "javascript",
54
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/js.mdx",
75
+ stub: getFeatureFlagDocs("javascript"),
55
76
  docs: "https://docs.growthbook.io/lib/js",
56
77
  };
57
78
  case ".vue":
58
79
  return {
59
80
  language: "vue",
60
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/vue.mdx",
81
+ stub: getFeatureFlagDocs("vue"),
61
82
  docs: "https://docs.growthbook.io/lib/vue",
62
83
  };
63
84
  case ".py":
64
85
  return {
65
86
  language: "python",
66
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/python.mdx",
87
+ stub: getFeatureFlagDocs("python"),
67
88
  docs: "https://docs.growthbook.io/lib/python",
68
89
  };
69
90
  case ".go":
70
91
  return {
71
92
  language: "go",
72
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/go.mdx",
93
+ stub: getFeatureFlagDocs("go"),
73
94
  docs: "https://docs.growthbook.io/lib/go",
74
95
  };
75
96
  case ".php":
76
97
  return {
77
98
  language: "php",
78
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/php.mdx",
99
+ stub: getFeatureFlagDocs("php"),
79
100
  docs: "https://docs.growthbook.io/lib/php",
80
101
  };
81
102
  case ".rb":
82
103
  return {
83
104
  language: "ruby",
84
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/ruby.mdx",
105
+ stub: getFeatureFlagDocs("ruby"),
85
106
  docs: "https://docs.growthbook.io/lib/ruby",
86
107
  };
87
108
  case ".java":
88
109
  return {
89
110
  language: "java",
90
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/java.mdx",
111
+ stub: getFeatureFlagDocs("java"),
91
112
  docs: "https://docs.growthbook.io/lib/java",
92
113
  };
93
114
  case ".cs":
94
115
  return {
95
116
  language: "csharp",
96
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/csharp.mdx",
117
+ stub: getFeatureFlagDocs("csharp"),
97
118
  docs: "https://docs.growthbook.io/lib/csharp",
98
119
  };
99
120
  case ".swift":
100
121
  return {
101
122
  language: "swift",
102
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/swift.mdx",
123
+ stub: getFeatureFlagDocs("swift"),
103
124
  docs: "https://docs.growthbook.io/lib/swift",
104
125
  };
105
126
  case ".ex":
106
127
  case ".exs":
107
128
  return {
108
129
  language: "elixir",
109
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/elixir.mdx",
130
+ stub: getFeatureFlagDocs("elixir"),
110
131
  docs: "https://docs.growthbook.io/lib/elixir",
111
132
  };
112
133
  case ".kt":
@@ -114,19 +135,19 @@ export function getDocsMetadata(extension) {
114
135
  case ".ktm":
115
136
  return {
116
137
  language: "kotlin",
117
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/kotlin.mdx",
138
+ stub: getFeatureFlagDocs("kotlin"),
118
139
  docs: "https://docs.growthbook.io/lib/kotlin",
119
140
  };
120
141
  case ".dart":
121
142
  return {
122
143
  language: "flutter",
123
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/flutter.mdx",
144
+ stub: getFeatureFlagDocs("flutter"),
124
145
  docs: "https://docs.growthbook.io/lib/flutter",
125
146
  };
126
147
  default:
127
148
  return {
128
149
  language: "unknown",
129
- md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/index.mdx",
150
+ stub: getFeatureFlagDocs("unknown"),
130
151
  docs: "https://docs.growthbook.io/lib/",
131
152
  };
132
153
  }
@@ -155,18 +176,6 @@ export async function searchGrowthBookDocs(query) {
155
176
  return [];
156
177
  }
157
178
  }
158
- export async function findImplementationDocs(extension) {
159
- const { md } = getDocsMetadata(extension);
160
- try {
161
- const response = await fetch(md);
162
- await handleResNotOk(response);
163
- const markdown = await response.text();
164
- return markdown;
165
- }
166
- catch (error) {
167
- return "Docs not found";
168
- }
169
- }
170
179
  export function generateLinkToGrowthBook(appOrigin, resource, id) {
171
180
  return `${appOrigin}/${resource}/${id}`;
172
181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growthbook/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "",
5
5
  "access": "public",
6
6
  "homepage": "https://github.com/growthbook/growthbook-mcp",
@@ -22,11 +22,12 @@
22
22
  "license": "MIT",
23
23
  "packageManager": "pnpm@10.6.1",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.10.2",
26
- "zod": "^3.22.4"
25
+ "@modelcontextprotocol/sdk": "^1.13.1",
26
+ "env-paths": "^3.0.0",
27
+ "zod": "^3.25.67"
27
28
  },
28
29
  "devDependencies": {
29
- "@types/node": "^22.15.3",
30
+ "@types/node": "^24.0.4",
30
31
  "typescript": "^5.8.3"
31
32
  },
32
33
  "type": "module"