@growthbook/mcp 0.1.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/LICENSE +21 -0
- package/README.md +140 -0
- package/build/index.js +55 -0
- package/build/tools/environments.js +27 -0
- package/build/tools/experiments.js +183 -0
- package/build/tools/features.js +260 -0
- package/build/tools/projects.js +35 -0
- package/build/tools/sdk-connections.js +131 -0
- package/build/tools/search.js +23 -0
- package/build/utils.js +172 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 GrowthBook
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# GrowthBook MCP Server
|
|
2
|
+
|
|
3
|
+
With the GrowthBook MCP server, you can interact with GrowthBook right from your LLM client. See experiment details, add a feature flag, and more.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Local Installation
|
|
8
|
+
|
|
9
|
+
1. Clone the repo
|
|
10
|
+
2. Run `npx tsc` to generate a build
|
|
11
|
+
|
|
12
|
+
**Environment Variables**
|
|
13
|
+
Use the following env variables to configure the MCP server.
|
|
14
|
+
|
|
15
|
+
| Variable Name | Status | Description |
|
|
16
|
+
| ------------- | -------- | ----------------------------------------------------------------- |
|
|
17
|
+
| GB_API_KEY | Required | A GrowthBook API key. |
|
|
18
|
+
| GB_USER | Required | Your name. Used when creating a feature flag. |
|
|
19
|
+
| GB_API_URL | Optional | Your GrowthBook API URL. Defaults to `https://api.growthbook.io`. |
|
|
20
|
+
| GB_APP_ORIGIN | Optional | Your GrowthBook app URL Defaults to `https://app.growthbook.io`. |
|
|
21
|
+
|
|
22
|
+
Find instructions below to add the MCP server to a client. Any client that supports MCP is also compatible. Consult its documentation for how to add the server.
|
|
23
|
+
|
|
24
|
+
### Cursor
|
|
25
|
+
|
|
26
|
+
1. Open **Cursor Settings** → **MCP**
|
|
27
|
+
2. Click **Add new global MCP server**
|
|
28
|
+
3. Add an entry for the GrowthBook MCP, following the pattern below:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"growthbook": {
|
|
34
|
+
"command": "node",
|
|
35
|
+
"args": ["ABSOLUTE_PATH_TO_THE_BUILT_MCP_SERVER"],
|
|
36
|
+
"env": {
|
|
37
|
+
"GB_API_KEY": "YOUR_API_KEY",
|
|
38
|
+
"GB_API_URL": "YOUR_API_URL",
|
|
39
|
+
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
40
|
+
"GB_USER": "YOUR_NAME"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
3. Save the settings.
|
|
48
|
+
|
|
49
|
+
You should now see a green active status after the server successfully connects!
|
|
50
|
+
|
|
51
|
+
### VS Code
|
|
52
|
+
|
|
53
|
+
1. Open **User Settings (JSON)**
|
|
54
|
+
2. Add an MCP entry:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
"mcp": {
|
|
58
|
+
"servers": {
|
|
59
|
+
"growthbook": {
|
|
60
|
+
"command": "node",
|
|
61
|
+
"args": [
|
|
62
|
+
"ABSOLUTE_PATH_TO_THE_BUILT_MCP_SERVER"
|
|
63
|
+
],
|
|
64
|
+
"env": {
|
|
65
|
+
"GB_API_KEY": "YOUR_API_KEY",
|
|
66
|
+
"GB_API_URL": "YOUR_API_URL",
|
|
67
|
+
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
68
|
+
"GB_USER": "YOUR_NAME"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Save your settings.
|
|
76
|
+
|
|
77
|
+
GrowthBook MCP is now ready to use in VS Code.
|
|
78
|
+
|
|
79
|
+
### Claude Desktop
|
|
80
|
+
|
|
81
|
+
1. **Open Settings** → **Developer**
|
|
82
|
+
2. Click **Edit Config**
|
|
83
|
+
3. Open `claude_desktop_config.json`
|
|
84
|
+
4. Add the following configuration:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"growthbook": {
|
|
90
|
+
"command": "node",
|
|
91
|
+
"args": ["ABSOLUTE_PATH_TO_THE_BUILT_MCP_SERVER"],
|
|
92
|
+
"env": {
|
|
93
|
+
"GB_API_KEY": "YOUR_API_KEY",
|
|
94
|
+
"GB_API_URL": "YOUR_API_URL",
|
|
95
|
+
"GB_APP_ORIGIN": "YOUR_APP_ORIGIN",
|
|
96
|
+
"GB_USER": "YOUR_NAME"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
5. Save the config and restart Claude
|
|
104
|
+
|
|
105
|
+
A hammer icon should appear in the chat window, indicating that your GrowthBook MCP server is connected and available!
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Tools
|
|
110
|
+
|
|
111
|
+
- **Feature Flags**
|
|
112
|
+
|
|
113
|
+
- `create_feature_flag`: Create, add, or wrap an element with a feature flag. Specify key, type, default value, and metadata.
|
|
114
|
+
- `get_feature_flags`: List all feature flags in your GrowthBook instance.
|
|
115
|
+
- `get_single_feature_flag`: Fetch details for a specific feature flag by ID.
|
|
116
|
+
- `get_stale_safe_rollouts`: List all safe rollout rules that have been rolled back or released.
|
|
117
|
+
- `create_force_rule`: Create a feature flag with a targeting condition.
|
|
118
|
+
- `generate_flag_types`: Generates types for feature flags
|
|
119
|
+
|
|
120
|
+
- **Experiments**
|
|
121
|
+
|
|
122
|
+
- `get_experiments`: List all experiments in GrowthBook.
|
|
123
|
+
- `get_experiment`: Fetch details for a specific experiment by ID.
|
|
124
|
+
- `get_attributes`: List all user attributes tracked in GrowthBook (useful for targeting).
|
|
125
|
+
|
|
126
|
+
- **Environments**
|
|
127
|
+
|
|
128
|
+
- `get_environments`: List all environments (e.g., production, staging) configured in GrowthBook.
|
|
129
|
+
|
|
130
|
+
- **Projects**
|
|
131
|
+
|
|
132
|
+
- `get_projects`: List all projects in your GrowthBook instance.
|
|
133
|
+
|
|
134
|
+
- **SDK Connections**
|
|
135
|
+
|
|
136
|
+
- `get_sdk_connections`: List all SDK connections (how GrowthBook connects to your apps).
|
|
137
|
+
- `create_sdk_connection`: Create a new SDK connection for your app, specifying language and environment.
|
|
138
|
+
|
|
139
|
+
- **Documentation Search**
|
|
140
|
+
- `search_growthbook_docs`: Search the GrowthBook documentation for information on how to use a feature, by keyword or question.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerEnvironmentTools } from "./tools/environments.js";
|
|
5
|
+
import { registerExperimentTools } from "./tools/experiments.js";
|
|
6
|
+
import { registerFeatureTools } from "./tools/features.js";
|
|
7
|
+
import { registerProjectTools } from "./tools/projects.js";
|
|
8
|
+
import { registerSdkConnectionTools } from "./tools/sdk-connections.js";
|
|
9
|
+
import { getApiKey, getApiUrl, getAppOrigin, getUser } from "./utils.js";
|
|
10
|
+
import { registerSearchTool } from "./tools/search.js";
|
|
11
|
+
export const baseApiUrl = getApiUrl();
|
|
12
|
+
export const apiKey = getApiKey();
|
|
13
|
+
export const appOrigin = getAppOrigin();
|
|
14
|
+
export const user = getUser();
|
|
15
|
+
// Create an MCP server
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: "GrowthBook MCP",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
}, {
|
|
20
|
+
instructions: "You are a helpful assistant that interacts with GrowthBook, an open source feature flagging and experimentation platform. You can use tools to create and manage feature flags, experiments, and environments. Note that experiments are also called a/b tests.",
|
|
21
|
+
});
|
|
22
|
+
registerEnvironmentTools({
|
|
23
|
+
server,
|
|
24
|
+
baseApiUrl,
|
|
25
|
+
apiKey,
|
|
26
|
+
});
|
|
27
|
+
registerProjectTools({
|
|
28
|
+
server,
|
|
29
|
+
baseApiUrl,
|
|
30
|
+
apiKey,
|
|
31
|
+
});
|
|
32
|
+
registerSdkConnectionTools({
|
|
33
|
+
server,
|
|
34
|
+
baseApiUrl,
|
|
35
|
+
apiKey,
|
|
36
|
+
});
|
|
37
|
+
registerFeatureTools({
|
|
38
|
+
server,
|
|
39
|
+
baseApiUrl,
|
|
40
|
+
apiKey,
|
|
41
|
+
appOrigin,
|
|
42
|
+
user,
|
|
43
|
+
});
|
|
44
|
+
registerExperimentTools({
|
|
45
|
+
server,
|
|
46
|
+
baseApiUrl,
|
|
47
|
+
apiKey,
|
|
48
|
+
appOrigin,
|
|
49
|
+
});
|
|
50
|
+
registerSearchTool({
|
|
51
|
+
server,
|
|
52
|
+
});
|
|
53
|
+
// Start receiving messages on stdin and sending messages on stdout
|
|
54
|
+
const transport = new StdioServerTransport();
|
|
55
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { handleResNotOk } from "../utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tool: get_environments
|
|
4
|
+
* Description: Fetches all environments from the GrowthBook API. Environments can be used to enable/disable feature flags per environment and control feature delivery segmentation.
|
|
5
|
+
*/
|
|
6
|
+
export function registerEnvironmentTools({ server, baseApiUrl, apiKey, }) {
|
|
7
|
+
server.tool("get_environments", "Fetches all environments from the GrowthBook API. GrowthBook comes with one environment by default (production), but you can add as many as you need. Feature flags can be enabled and disabled on a per-environment basis. You can also set the default feature state for any new environment. Additionally, you can scope environments to only be available in specific projects, allowing for further control and segmentation over feature delivery.", {}, async () => {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${baseApiUrl}/api/v1/environments`, {
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${apiKey}`,
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
await handleResNotOk(res);
|
|
16
|
+
const data = await res.json();
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { findImplementationDocs, generateLinkToGrowthBook, getDocsMetadata, handleResNotOk, } from "../utils.js";
|
|
3
|
+
export function registerExperimentTools({ server, baseApiUrl, apiKey, appOrigin, }) {
|
|
4
|
+
/**
|
|
5
|
+
* Tool: get_experiments
|
|
6
|
+
* Description: Fetches all experiments from the GrowthBook API.
|
|
7
|
+
*/
|
|
8
|
+
server.tool("get_experiments", "Fetches all experiments from the GrowthBook API", {
|
|
9
|
+
limit: z.number().optional().default(100),
|
|
10
|
+
offset: z.number().optional().default(0),
|
|
11
|
+
project: z.string().optional(),
|
|
12
|
+
}, async ({ limit, offset, project }) => {
|
|
13
|
+
try {
|
|
14
|
+
const queryParams = new URLSearchParams({
|
|
15
|
+
limit: limit?.toString(),
|
|
16
|
+
offset: offset?.toString(),
|
|
17
|
+
});
|
|
18
|
+
if (project)
|
|
19
|
+
queryParams.append("project", project);
|
|
20
|
+
const res = await fetch(`${baseApiUrl}/api/v1/experiments?${queryParams.toString()}`, {
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: `Bearer ${apiKey}`,
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
await handleResNotOk(res);
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error("Error fetching experiments:", error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Tool: create_force_rule
|
|
39
|
+
* Description: Creates a new force rule on an existing feature. A force rule sets a feature to a specific value for a specific environment based on a condition. For A/B tests and experiments, use create_experiment instead.
|
|
40
|
+
*/
|
|
41
|
+
server.tool("create_force_rule", "Create a new force rule on an existing feature. If the existing feature isn't apparent, create a new feature using create_feature_flag first. A force rule sets a feature to a specific value for a specific environment based on a condition. For A/B tests and experiments, use create_experiment instead.", {
|
|
42
|
+
featureId: z
|
|
43
|
+
.string()
|
|
44
|
+
.describe("The ID of the feature to create the rule on"),
|
|
45
|
+
description: z.string().optional(),
|
|
46
|
+
condition: z
|
|
47
|
+
.string()
|
|
48
|
+
.describe("Applied to everyone by default. Write conditions in MongoDB-style query syntax.")
|
|
49
|
+
.optional(),
|
|
50
|
+
value: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe("The type of the value should match the feature type"),
|
|
53
|
+
environments: z.string().array(),
|
|
54
|
+
fileExtension: z
|
|
55
|
+
.enum([
|
|
56
|
+
".tsx",
|
|
57
|
+
".jsx",
|
|
58
|
+
".ts",
|
|
59
|
+
".js",
|
|
60
|
+
".vue",
|
|
61
|
+
".py",
|
|
62
|
+
".go",
|
|
63
|
+
".php",
|
|
64
|
+
".rb",
|
|
65
|
+
".java",
|
|
66
|
+
".cs",
|
|
67
|
+
])
|
|
68
|
+
.describe("The extension of the current file. If it's unclear, ask the user."),
|
|
69
|
+
}, async ({ featureId, description, condition, value, environments, fileExtension, }) => {
|
|
70
|
+
try {
|
|
71
|
+
const payload = {
|
|
72
|
+
// Loop through the environments and create a rule for each one keyed by environment name
|
|
73
|
+
environments: environments.reduce((acc, env) => {
|
|
74
|
+
acc[env] = {
|
|
75
|
+
enabled: true,
|
|
76
|
+
rules: [
|
|
77
|
+
{
|
|
78
|
+
type: "force",
|
|
79
|
+
description,
|
|
80
|
+
condition,
|
|
81
|
+
value,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
return acc;
|
|
86
|
+
}, {}),
|
|
87
|
+
};
|
|
88
|
+
const res = await fetch(`${baseApiUrl}/api/v1/features/${featureId}`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${apiKey}`,
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(payload),
|
|
95
|
+
});
|
|
96
|
+
await handleResNotOk(res);
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
const linkToGrowthBook = generateLinkToGrowthBook(appOrigin, "features", featureId);
|
|
99
|
+
const docsText = await findImplementationDocs(fileExtension);
|
|
100
|
+
const { docs, language } = getDocsMetadata(fileExtension);
|
|
101
|
+
const text = `This is the API response: ${JSON.stringify(data, null, 2)}
|
|
102
|
+
|
|
103
|
+
Additionally, here is a template of what to show to the user:
|
|
104
|
+
|
|
105
|
+
**✅ Your feature flag \`my-flag-name\` is ready!.**
|
|
106
|
+
[View it in GrowthBook](${linkToGrowthBook})
|
|
107
|
+
|
|
108
|
+
**How to use it in your code:**
|
|
109
|
+
(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)
|
|
110
|
+
|
|
111
|
+
${docsText}
|
|
112
|
+
|
|
113
|
+
**Want to learn more?**
|
|
114
|
+
See the [GrowthBook ${language} docs](${docs})
|
|
115
|
+
`;
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text }],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* Tool: get_experiment
|
|
128
|
+
* Description: Gets a single experiment from GrowthBook by its ID.
|
|
129
|
+
*/
|
|
130
|
+
server.tool("get_experiment", "Gets a single experiment from GrowthBook", {
|
|
131
|
+
experimentId: z.string().describe("The ID of the experiment to get"),
|
|
132
|
+
}, async ({ experimentId }) => {
|
|
133
|
+
try {
|
|
134
|
+
const res = await fetch(`${baseApiUrl}/api/v1/experiments/${experimentId}`, {
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${apiKey}`,
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
await handleResNotOk(res);
|
|
141
|
+
const data = await res.json();
|
|
142
|
+
const linkToGrowthBook = generateLinkToGrowthBook(appOrigin, "experiment", experimentId);
|
|
143
|
+
const text = `
|
|
144
|
+
${JSON.stringify(data, null, 2)}
|
|
145
|
+
|
|
146
|
+
[View the experiment in GrowthBook](${linkToGrowthBook})
|
|
147
|
+
`;
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text }],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* Tool: get_attributes
|
|
160
|
+
* Description: Gets all attributes from the GrowthBook API.
|
|
161
|
+
*/
|
|
162
|
+
server.tool("get_attributes", "Get all attributes", {}, async () => {
|
|
163
|
+
try {
|
|
164
|
+
const queryParams = new URLSearchParams();
|
|
165
|
+
queryParams.append("limit", "100");
|
|
166
|
+
const res = await fetch(`${baseApiUrl}/api/v1/attributes?${queryParams.toString()}`, {
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${apiKey}`,
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
await handleResNotOk(res);
|
|
173
|
+
const data = await res.json();
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error("Error fetching attributes:", error);
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getDocsMetadata, findImplementationDocs, handleResNotOk, generateLinkToGrowthBook, } from "../utils.js";
|
|
3
|
+
export function registerFeatureTools({ server, baseApiUrl, apiKey, appOrigin, user, }) {
|
|
4
|
+
/**
|
|
5
|
+
* 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
|
+
*/
|
|
8
|
+
server.tool("create_feature_flag", "Create, add, or wrap an element with a feature flag.", {
|
|
9
|
+
id: z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(/^[a-zA-Z0-9_-]+$/, "Feature key can only include letters, numbers, hyphens, and underscores.")
|
|
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
|
+
description: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.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"),
|
|
28
|
+
valueType: z
|
|
29
|
+
.enum(["string", "number", "boolean", "json"])
|
|
30
|
+
.describe("The value type the feature flag will return"),
|
|
31
|
+
defaultValue: z
|
|
32
|
+
.string()
|
|
33
|
+
.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
|
+
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
|
+
])
|
|
52
|
+
.describe("The extension of the current file. If it's unclear, ask the user."),
|
|
53
|
+
}, async ({ id, archived, description, project, valueType, defaultValue, tags, fileExtension, }) => {
|
|
54
|
+
const payload = {
|
|
55
|
+
id,
|
|
56
|
+
archived,
|
|
57
|
+
description,
|
|
58
|
+
owner: user,
|
|
59
|
+
project,
|
|
60
|
+
valueType,
|
|
61
|
+
defaultValue,
|
|
62
|
+
tags,
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(`${baseApiUrl}/api/v1/features`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Bearer ${apiKey}`,
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(payload),
|
|
72
|
+
});
|
|
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
|
+
}
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
const docsText = await findImplementationDocs(fileExtension);
|
|
89
|
+
const { docs, language } = getDocsMetadata(fileExtension);
|
|
90
|
+
const linkToGrowthBook = generateLinkToGrowthBook(appOrigin, "features", id);
|
|
91
|
+
const text = `This is the API response: ${JSON.stringify(data, null, 2)}
|
|
92
|
+
|
|
93
|
+
Additionally, here is a template of what to show to the user:
|
|
94
|
+
|
|
95
|
+
**✅ Your feature flag \`my-flag-name\` is ready!.**
|
|
96
|
+
[View it in GrowthBook](${linkToGrowthBook})
|
|
97
|
+
|
|
98
|
+
**How to use it in your code:**
|
|
99
|
+
(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
|
+
|
|
101
|
+
${docsText}
|
|
102
|
+
|
|
103
|
+
**Want to learn more?**
|
|
104
|
+
See the [GrowthBook ${language} docs](${docs})
|
|
105
|
+
`;
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text }],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error("Error creating feature flag:", error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
/**
|
|
116
|
+
* Tool: get_feature_flags
|
|
117
|
+
* Description: Fetches all feature flags from the GrowthBook API, with optional limit, offset, and project filtering.
|
|
118
|
+
*/
|
|
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.", {
|
|
120
|
+
limit: z.number().optional().default(100),
|
|
121
|
+
offset: z.number().optional().default(0),
|
|
122
|
+
project: z.string().optional(),
|
|
123
|
+
}, async ({ limit, offset, project }) => {
|
|
124
|
+
try {
|
|
125
|
+
const queryParams = new URLSearchParams({
|
|
126
|
+
limit: limit?.toString(),
|
|
127
|
+
offset: offset?.toString(),
|
|
128
|
+
});
|
|
129
|
+
if (project)
|
|
130
|
+
queryParams.append("project", project);
|
|
131
|
+
const res = await fetch(`${baseApiUrl}/api/v1/features?${queryParams.toString()}`, {
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${apiKey}`,
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
await handleResNotOk(res);
|
|
138
|
+
const data = await res.json();
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error("Error fetching flags:", error);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
/**
|
|
149
|
+
* Tool: get_single_feature_flag
|
|
150
|
+
* Description: Fetches a specific feature flag from the GrowthBook API by its ID, with optional project filtering.
|
|
151
|
+
*/
|
|
152
|
+
server.tool("get_single_feature_flag", "Fetches a specific feature flag from the GrowthBook API", {
|
|
153
|
+
id: z.string().describe("The ID of the feature flag"),
|
|
154
|
+
project: z.string().optional(),
|
|
155
|
+
}, async ({ id, project }) => {
|
|
156
|
+
try {
|
|
157
|
+
const queryParams = new URLSearchParams();
|
|
158
|
+
if (project)
|
|
159
|
+
queryParams.append("project", project);
|
|
160
|
+
const res = await fetch(`${baseApiUrl}/api/v1/features/${id}?${queryParams.toString()}`, {
|
|
161
|
+
headers: {
|
|
162
|
+
Authorization: `Bearer ${apiKey}`,
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
await handleResNotOk(res);
|
|
167
|
+
const data = await res.json();
|
|
168
|
+
const linkToGrowthBook = generateLinkToGrowthBook(appOrigin, "features", id);
|
|
169
|
+
const text = `
|
|
170
|
+
${JSON.stringify(data.feature, null, 2)}
|
|
171
|
+
|
|
172
|
+
Share information about the feature flag with the user. In particular, give details about the enabled environments,
|
|
173
|
+
rules for each environment, and the default value. If the feature flag is archived or doesnt exist, inform the user and
|
|
174
|
+
ask if they want to remove references to the feature flag from the codebase.
|
|
175
|
+
|
|
176
|
+
[View it in GrowthBook](${linkToGrowthBook})
|
|
177
|
+
`;
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text }],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error("Error fetching flags:", error);
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* 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
|
+
*/
|
|
191
|
+
server.tool("get_stale_safe_rollouts", "Fetches all complete safe rollouts (rolled-back or released) from the GrowthBook API", {
|
|
192
|
+
limit: z.number().optional().default(100),
|
|
193
|
+
offset: z.number().optional().default(0),
|
|
194
|
+
project: z.string().optional(),
|
|
195
|
+
}, async ({ limit, offset, project }) => {
|
|
196
|
+
try {
|
|
197
|
+
const queryParams = new URLSearchParams({
|
|
198
|
+
limit: limit?.toString(),
|
|
199
|
+
offset: offset?.toString(),
|
|
200
|
+
});
|
|
201
|
+
if (project)
|
|
202
|
+
queryParams.append("project", project);
|
|
203
|
+
const res = await fetch(`${baseApiUrl}/api/v1/features?${queryParams.toString()}`, {
|
|
204
|
+
headers: {
|
|
205
|
+
Authorization: `Bearer ${apiKey}`,
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
await handleResNotOk(res);
|
|
210
|
+
const data = await res.json();
|
|
211
|
+
const filteredSafeRollouts = data.features.filter((feature) => {
|
|
212
|
+
const envs = feature.environments;
|
|
213
|
+
if (!envs)
|
|
214
|
+
return false;
|
|
215
|
+
return Object.values(envs).some((env) => {
|
|
216
|
+
const rules = env.rules;
|
|
217
|
+
if (!rules)
|
|
218
|
+
return false;
|
|
219
|
+
return rules.some((rule) => {
|
|
220
|
+
return (rule.type === "safe-rollout" &&
|
|
221
|
+
(rule.status === "rolled-back" || rule.status === "released"));
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
const text = `
|
|
226
|
+
${JSON.stringify(filteredSafeRollouts, null, 2)}
|
|
227
|
+
|
|
228
|
+
Share information about the rolled-back or released safe rollout rules with the user. Safe Rollout rules are stored under
|
|
229
|
+
environmentSettings, keyed by environment and are within the rules array with a type of "safe-rollout". Ask the user if they
|
|
230
|
+
would like to remove references to the feature associated with the rolled-back or released safe rollout rules and if they do,
|
|
231
|
+
remove the references and associated GrowthBook code and replace the values with controlValue if the safe rollout rule is rolled-back or with the
|
|
232
|
+
variationValue if the safe rollout is released. In addition to the current file, you may need to update other files in the codebase.
|
|
233
|
+
`;
|
|
234
|
+
return {
|
|
235
|
+
content: [{ type: "text", text }],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.error("Error fetching stale safe rollouts:", error);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
/**
|
|
244
|
+
* Tool: generate_flag_types
|
|
245
|
+
* Description: Generates types for feature flags using the GrowthBook CLI.
|
|
246
|
+
*/
|
|
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
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { handleResNotOk } from "../utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Tool: get_projects
|
|
5
|
+
* Description: Fetches all projects from the GrowthBook API, with optional limit and offset for pagination.
|
|
6
|
+
*/
|
|
7
|
+
export function registerProjectTools({ server, baseApiUrl, apiKey, }) {
|
|
8
|
+
server.tool("get_projects", "Fetches all projects from the GrowthBook API", {
|
|
9
|
+
limit: z.number().optional().default(10),
|
|
10
|
+
offset: z.number().optional().default(0),
|
|
11
|
+
}, async ({ limit, offset }) => {
|
|
12
|
+
const queryParams = new URLSearchParams({
|
|
13
|
+
limit: limit.toString(),
|
|
14
|
+
offset: offset.toString(),
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(`${baseApiUrl}/api/v1/projects?${queryParams.toString()}`, {
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${apiKey}`,
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
await handleResNotOk(res);
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { handleResNotOk } from "../utils.js";
|
|
3
|
+
export function registerSdkConnectionTools({ server, baseApiUrl, apiKey, }) {
|
|
4
|
+
/**
|
|
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
|
+
*/
|
|
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 `, {
|
|
12
|
+
limit: z.number().optional().default(100),
|
|
13
|
+
offset: z.number().optional().default(0),
|
|
14
|
+
project: z.string().optional(),
|
|
15
|
+
}, async ({ limit, offset, project }) => {
|
|
16
|
+
try {
|
|
17
|
+
const queryParams = new URLSearchParams({
|
|
18
|
+
limit: limit?.toString(),
|
|
19
|
+
offset: offset?.toString(),
|
|
20
|
+
});
|
|
21
|
+
if (project)
|
|
22
|
+
queryParams.append("project", project);
|
|
23
|
+
const res = await fetch(`${baseApiUrl}/api/v1/sdk-connections?${queryParams.toString()}`, {
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${apiKey}`,
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
await handleResNotOk(res);
|
|
30
|
+
const data = await res.json();
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* 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
|
+
*/
|
|
46
|
+
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
|
+
name: z
|
|
48
|
+
.string()
|
|
49
|
+
.describe("Name of the SDK connection in GrowthBook. Should reflect the current project."),
|
|
50
|
+
language: z
|
|
51
|
+
.enum([
|
|
52
|
+
"nocode-webflow",
|
|
53
|
+
"nocode-wordpress",
|
|
54
|
+
"nocode-shopify",
|
|
55
|
+
"nocode-other",
|
|
56
|
+
"javascript",
|
|
57
|
+
"nodejs",
|
|
58
|
+
"react",
|
|
59
|
+
"php",
|
|
60
|
+
"ruby",
|
|
61
|
+
"python",
|
|
62
|
+
"go",
|
|
63
|
+
"java",
|
|
64
|
+
"csharp",
|
|
65
|
+
"android",
|
|
66
|
+
"ios",
|
|
67
|
+
"flutter",
|
|
68
|
+
"elixir",
|
|
69
|
+
"edge-cloudflare",
|
|
70
|
+
"edge-fastly",
|
|
71
|
+
"edge-lambda",
|
|
72
|
+
"edge-other",
|
|
73
|
+
"other",
|
|
74
|
+
])
|
|
75
|
+
.describe("The language of the SDK. Either 'javascript' or 'typescript'."),
|
|
76
|
+
environment: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("The environment associated with the SDK connection."),
|
|
80
|
+
}, async ({ name, language, environment }) => {
|
|
81
|
+
if (!environment) {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`${baseApiUrl}/api/v1/environments`, {
|
|
84
|
+
headers: {
|
|
85
|
+
Authorization: `Bearer ${apiKey}`,
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
await handleResNotOk(res);
|
|
90
|
+
const data = await res.json();
|
|
91
|
+
const text = `${JSON.stringify(data, null, 2)}
|
|
92
|
+
|
|
93
|
+
Here is the list of environments. Ask the user to select one and use the key in the create_sdk_connection tool.
|
|
94
|
+
`;
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const payload = {
|
|
106
|
+
name,
|
|
107
|
+
language,
|
|
108
|
+
environment,
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const res = await fetch(`${baseApiUrl}/api/v1/sdk-connections`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: {
|
|
114
|
+
Authorization: `Bearer ${apiKey}`,
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify(payload),
|
|
118
|
+
});
|
|
119
|
+
await handleResNotOk(res);
|
|
120
|
+
const data = await res.json();
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchGrowthBookDocs } from "../utils.js";
|
|
3
|
+
/**
|
|
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
|
+
*/
|
|
7
|
+
export function registerSearchTool({ server }) {
|
|
8
|
+
server.tool("search_growthbook_docs", "Search the GrowthBook docs on how to use a feature", {
|
|
9
|
+
query: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("The search query to look up in the GrowthBook docs."),
|
|
12
|
+
}, async ({ query }) => {
|
|
13
|
+
const hits = await searchGrowthBookDocs(query);
|
|
14
|
+
return {
|
|
15
|
+
content: hits.slice(0, 5).map((hit) => ({
|
|
16
|
+
type: "text",
|
|
17
|
+
text: hit.title
|
|
18
|
+
? `${hit.title}: ${hit.url}`
|
|
19
|
+
: hit.url || JSON.stringify(hit),
|
|
20
|
+
})),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export async function handleResNotOk(res) {
|
|
2
|
+
if (!res.ok) {
|
|
3
|
+
let errorMessage = `HTTP ${res.status} ${res.statusText}`;
|
|
4
|
+
try {
|
|
5
|
+
const errorBody = await res.json();
|
|
6
|
+
errorMessage += `: ${JSON.stringify(errorBody)}`;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
// fallback to text if not JSON
|
|
10
|
+
const errorText = await res.text();
|
|
11
|
+
if (errorText)
|
|
12
|
+
errorMessage += `: ${errorText}`;
|
|
13
|
+
}
|
|
14
|
+
throw new Error(errorMessage);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function getApiKey() {
|
|
18
|
+
const apiKey = process.env.GB_API_KEY;
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error("GB_API_KEY environment variable is required");
|
|
21
|
+
}
|
|
22
|
+
return apiKey;
|
|
23
|
+
}
|
|
24
|
+
export function getApiUrl() {
|
|
25
|
+
const defaultApiUrl = "https://api.growthbook.io";
|
|
26
|
+
const userApiUrl = process.env.GB_API_URL;
|
|
27
|
+
return `${userApiUrl || defaultApiUrl}`;
|
|
28
|
+
}
|
|
29
|
+
export function getAppOrigin() {
|
|
30
|
+
const defaultAppOrigin = "https://app.growthbook.io";
|
|
31
|
+
const userAppOrigin = process.env.GB_APP_ORIGIN;
|
|
32
|
+
return `${userAppOrigin || defaultAppOrigin}`;
|
|
33
|
+
}
|
|
34
|
+
export function getUser() {
|
|
35
|
+
const user = process.env.GB_USER;
|
|
36
|
+
if (!user) {
|
|
37
|
+
throw new Error("GB_USER environment variable is required");
|
|
38
|
+
}
|
|
39
|
+
return user;
|
|
40
|
+
}
|
|
41
|
+
export function getDocsMetadata(extension) {
|
|
42
|
+
switch (extension) {
|
|
43
|
+
case ".tsx":
|
|
44
|
+
case ".jsx":
|
|
45
|
+
return {
|
|
46
|
+
language: "react",
|
|
47
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/react.mdx",
|
|
48
|
+
docs: "https://docs.growthbook.io/lib/react",
|
|
49
|
+
};
|
|
50
|
+
case ".ts":
|
|
51
|
+
case ".js":
|
|
52
|
+
return {
|
|
53
|
+
language: "javascript",
|
|
54
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/js.mdx",
|
|
55
|
+
docs: "https://docs.growthbook.io/lib/js",
|
|
56
|
+
};
|
|
57
|
+
case ".vue":
|
|
58
|
+
return {
|
|
59
|
+
language: "vue",
|
|
60
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/vue.mdx",
|
|
61
|
+
docs: "https://docs.growthbook.io/lib/vue",
|
|
62
|
+
};
|
|
63
|
+
case ".py":
|
|
64
|
+
return {
|
|
65
|
+
language: "python",
|
|
66
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/python.mdx",
|
|
67
|
+
docs: "https://docs.growthbook.io/lib/python",
|
|
68
|
+
};
|
|
69
|
+
case ".go":
|
|
70
|
+
return {
|
|
71
|
+
language: "go",
|
|
72
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/go.mdx",
|
|
73
|
+
docs: "https://docs.growthbook.io/lib/go",
|
|
74
|
+
};
|
|
75
|
+
case ".php":
|
|
76
|
+
return {
|
|
77
|
+
language: "php",
|
|
78
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/php.mdx",
|
|
79
|
+
docs: "https://docs.growthbook.io/lib/php",
|
|
80
|
+
};
|
|
81
|
+
case ".rb":
|
|
82
|
+
return {
|
|
83
|
+
language: "ruby",
|
|
84
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/ruby.mdx",
|
|
85
|
+
docs: "https://docs.growthbook.io/lib/ruby",
|
|
86
|
+
};
|
|
87
|
+
case ".java":
|
|
88
|
+
return {
|
|
89
|
+
language: "java",
|
|
90
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/java.mdx",
|
|
91
|
+
docs: "https://docs.growthbook.io/lib/java",
|
|
92
|
+
};
|
|
93
|
+
case ".cs":
|
|
94
|
+
return {
|
|
95
|
+
language: "csharp",
|
|
96
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/csharp.mdx",
|
|
97
|
+
docs: "https://docs.growthbook.io/lib/csharp",
|
|
98
|
+
};
|
|
99
|
+
case ".swift":
|
|
100
|
+
return {
|
|
101
|
+
language: "swift",
|
|
102
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/swift.mdx",
|
|
103
|
+
docs: "https://docs.growthbook.io/lib/swift",
|
|
104
|
+
};
|
|
105
|
+
case ".ex":
|
|
106
|
+
case ".exs":
|
|
107
|
+
return {
|
|
108
|
+
language: "elixir",
|
|
109
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/elixir.mdx",
|
|
110
|
+
docs: "https://docs.growthbook.io/lib/elixir",
|
|
111
|
+
};
|
|
112
|
+
case ".kt":
|
|
113
|
+
case ".kts":
|
|
114
|
+
case ".ktm":
|
|
115
|
+
return {
|
|
116
|
+
language: "kotlin",
|
|
117
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/kotlin.mdx",
|
|
118
|
+
docs: "https://docs.growthbook.io/lib/kotlin",
|
|
119
|
+
};
|
|
120
|
+
case ".dart":
|
|
121
|
+
return {
|
|
122
|
+
language: "flutter",
|
|
123
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/flutter.mdx",
|
|
124
|
+
docs: "https://docs.growthbook.io/lib/flutter",
|
|
125
|
+
};
|
|
126
|
+
default:
|
|
127
|
+
return {
|
|
128
|
+
language: "unknown",
|
|
129
|
+
md: "https://raw.githubusercontent.com/growthbook/growthbook/main/docs/docs/lib/index.mdx",
|
|
130
|
+
docs: "https://docs.growthbook.io/lib/",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export async function searchGrowthBookDocs(query) {
|
|
135
|
+
const APPLICATION_ID = "MN7ZMY63CG";
|
|
136
|
+
const API_KEY = "e17ebcbd97bce29ad0bdec269770e9df";
|
|
137
|
+
const INDEX_NAME = "growthbook";
|
|
138
|
+
const url = `https://${APPLICATION_ID}-dsn.algolia.net/1/indexes/${INDEX_NAME}/query`;
|
|
139
|
+
try {
|
|
140
|
+
const response = await fetch(url, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"X-Algolia-API-Key": API_KEY,
|
|
144
|
+
"X-Algolia-Application-Id": APPLICATION_ID,
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({ query }),
|
|
148
|
+
});
|
|
149
|
+
await handleResNotOk(response);
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
const hits = data.hits || [];
|
|
152
|
+
return hits;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
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
|
+
export function generateLinkToGrowthBook(appOrigin, resource, id) {
|
|
171
|
+
return `${appOrigin}/${resource}/${id}`;
|
|
172
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@growthbook/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"access": "public",
|
|
6
|
+
"homepage": "https://github.com/growthbook/growthbook-mcp",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"bump:patch": "pnpm version patch --no-git-tag-version",
|
|
11
|
+
"bump:minor": "pnpm version minor --no-git-tag-version",
|
|
12
|
+
"bump:major": "pnpm version major --no-git-tag-version"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"mcp": "build/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"build"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [],
|
|
21
|
+
"author": "GrowthBook",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"packageManager": "pnpm@10.6.1",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.10.2",
|
|
26
|
+
"zod": "^3.22.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.15.3",
|
|
30
|
+
"typescript": "^5.8.3"
|
|
31
|
+
},
|
|
32
|
+
"type": "module"
|
|
33
|
+
}
|