@growthbook/mcp 1.3.0 → 1.4.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.
- package/package.json +6 -2
- package/server/index.js +11 -4
- package/server/prompts/experiment-prompts.js +30 -0
- package/server/tools/defaults.js +12 -12
- package/server/tools/environments.js +3 -3
- package/server/tools/experiments/experiment-summary.js +494 -0
- package/server/tools/{experiments.js → experiments/experiments.js} +95 -29
- package/server/tools/features.js +12 -12
- package/server/tools/metrics.js +7 -9
- package/server/tools/projects.js +3 -3
- package/server/tools/sdk-connections.js +7 -7
- package/server/tools/search.js +46 -8
- package/server/types/types.js +1 -0
- package/server/utils.js +153 -5
- package/server/prompts/experiment-analysis.js +0 -13
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growthbook/mcp",
|
|
3
|
-
"
|
|
3
|
+
"mcpName": "io.github.growthbook/growthbook-mcp",
|
|
4
|
+
"version": "1.4.2",
|
|
4
5
|
"description": "MCP Server for interacting with GrowthBook",
|
|
5
6
|
"access": "public",
|
|
6
7
|
"homepage": "https://github.com/growthbook/growthbook-mcp",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/growthbook/growthbook-mcp"
|
|
11
|
+
},
|
|
7
12
|
"scripts": {
|
|
8
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
9
13
|
"build": "tsc",
|
|
10
14
|
"dev": "tsc --watch",
|
|
11
15
|
"bump:patch": "npm version patch --no-git-tag-version",
|
package/server/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { registerEnvironmentTools } from "./tools/environments.js";
|
|
5
|
-
import { registerExperimentTools } from "./tools/experiments.js";
|
|
5
|
+
import { registerExperimentTools } from "./tools/experiments/experiments.js";
|
|
6
6
|
import { registerFeatureTools } from "./tools/features.js";
|
|
7
7
|
import { registerProjectTools } from "./tools/projects.js";
|
|
8
8
|
import { registerSdkConnectionTools } from "./tools/sdk-connections.js";
|
|
@@ -10,7 +10,8 @@ import { getApiKey, getApiUrl, getAppOrigin } from "./utils.js";
|
|
|
10
10
|
import { registerSearchTools } from "./tools/search.js";
|
|
11
11
|
import { registerDefaultsTools } from "./tools/defaults.js";
|
|
12
12
|
import { registerMetricsTools } from "./tools/metrics.js";
|
|
13
|
-
import {
|
|
13
|
+
import { registerExperimentPrompts } from "./prompts/experiment-prompts.js";
|
|
14
|
+
import packageDetails from "../package.json" with { type: "json" };
|
|
14
15
|
export const baseApiUrl = getApiUrl();
|
|
15
16
|
export const apiKey = getApiKey();
|
|
16
17
|
export const appOrigin = getAppOrigin();
|
|
@@ -21,7 +22,9 @@ if (!user) {
|
|
|
21
22
|
// Create an MCP server
|
|
22
23
|
const server = new McpServer({
|
|
23
24
|
name: "GrowthBook MCP",
|
|
24
|
-
version:
|
|
25
|
+
version: packageDetails.version,
|
|
26
|
+
title: "GrowthBook MCP",
|
|
27
|
+
websiteUrl: "https://growthbook.io",
|
|
25
28
|
}, {
|
|
26
29
|
instructions: `You are a helpful assistant that interacts with GrowthBook, an open source feature flagging and experimentation platform. You can create and manage feature flags, experiments (A/B tests), and other resources associated with GrowthBook.
|
|
27
30
|
|
|
@@ -51,6 +54,10 @@ const server = new McpServer({
|
|
|
51
54
|
- Feature flags and experiments require a fileExtension parameter for proper code integration
|
|
52
55
|
- Always review generated GrowthBook links with users so they can launch experiments
|
|
53
56
|
- When experiments are "draft", users must visit GrowthBook to review and launch them`,
|
|
57
|
+
capabilities: {
|
|
58
|
+
tools: {},
|
|
59
|
+
prompts: {},
|
|
60
|
+
},
|
|
54
61
|
});
|
|
55
62
|
registerEnvironmentTools({
|
|
56
63
|
server,
|
|
@@ -96,7 +103,7 @@ registerMetricsTools({
|
|
|
96
103
|
appOrigin,
|
|
97
104
|
user,
|
|
98
105
|
});
|
|
99
|
-
|
|
106
|
+
registerExperimentPrompts({
|
|
100
107
|
server,
|
|
101
108
|
});
|
|
102
109
|
// Start receiving messages on stdin and sending messages on stdout
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function registerExperimentPrompts({ server }) {
|
|
2
|
+
server.registerPrompt("experiment-analysis", {
|
|
3
|
+
title: "Analyze my experiment program",
|
|
4
|
+
description: "Analyze recent experiments and give me actionable advice",
|
|
5
|
+
}, () => ({
|
|
6
|
+
messages: [
|
|
7
|
+
{
|
|
8
|
+
role: "user",
|
|
9
|
+
content: {
|
|
10
|
+
type: "text",
|
|
11
|
+
text: "Use GrowthBook to fetch my recent experiments. Analyze the experiments and tell me:\n\n1. Which experiment types are actually worth running vs. theater?\n\n2. What's the one pattern in our losses that we're blind to?\n\n3. If you could only run 3 experiments next quarter based on these results, what would they be and why?\n\n4. What's the biggest methodological risk in our current approach that could be invalidating results?\n\nBe specific. Use the actual data. Don't give me generic advice.",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
}));
|
|
16
|
+
server.registerPrompt("wrapped", {
|
|
17
|
+
title: "GrowthBook Wrapped 2025",
|
|
18
|
+
description: "Generate a 'GrowthBook Wrapped' year-in-review for my experimentation program",
|
|
19
|
+
}, () => ({
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
role: "user",
|
|
23
|
+
content: {
|
|
24
|
+
type: "text",
|
|
25
|
+
text: "**Role:** Expert Frontend Developer & Data Storyteller\n**Task:** Generate a 'GrowthBook Wrapped 2025' interactive React slideshow component.\n**Aesthetic:** Spotify Wrapped meets Glassmorphism — dark mode, gradient-rich, bold typography.\n\n---\n\n## Phase 0: Brand & Design Constraints\n\n### Color Palette (Use GrowthBook Brand Colors)\n| Purpose | Colors |\n|---------|--------|\n| **Primary Gradient** | `#7B45EA` (purple) → `#2076FF` (blue) → `#06B8F4` (cyan) |\n| **Wins/Success** | Emerald/Teal gradients (`from-emerald-500 to-teal-600`) |\n| **Losses/Learnings** | Rose/Amber gradients (`from-rose-500 to-amber-600`) |\n| **Neutral/Stats** | Zinc/Slate gradients (`from-zinc-800 to-slate-900`) |\n\n*Constraint:* Never use flat solid backgrounds. Always use deep, rich gradients (e.g., `bg-gradient-to-br from-indigo-950 via-purple-900 to-black`).\n\n### Typography\n| Element | Style |\n|---------|-------|\n| **Hero Metrics** | `text-7xl md:text-8xl font-black tracking-tighter` |\n| **Headlines** | `text-2xl md:text-3xl font-bold leading-tight` |\n| **Labels** | `text-xs uppercase tracking-widest text-white/60` |\n| **Body** | `text-base md:text-lg text-white/80` |\n\n**Fonts:** Import from Google Fonts:\n- Display/Metrics: `Inter` (weight 800)\n- Body: `Inter` (weights 400, 500)\n- Mono: `IBM Plex Mono` (weight 400)\n\n### Visual Motifs\n- **Glassmorphism containers:** `bg-white/10 backdrop-blur-xl border border-white/20 rounded-3xl`\n- **Glow effects:** Use `shadow-[0_0_60px_-15px_rgba(123,69,234,0.5)]` for emphasis\n\n---\n\n## Phase 1: Data Acquisition & Processing\n\n### Step 1: Fetch Data\n1. Call `get_experiments` with `{ mode: 'summary', limit: 100 }`.\n2. Call `get_projects` to map project IDs to human-readable names.\n\n*Note: This may take 30-60 seconds. Tell the user to hang tight* \n\n### Step 2: Calculate Metrics\n\n**A. Summary Stats**\n| Variable | Calculation |\n|----------|-------------|\n| `total` | `summary.total` (stopped experiments only) |\n| `drafts` | `_meta.excluded.draft` |\n| `running` | `_meta.excluded.running` |\n| `totalUsers` | `summary.totalUsers` |\n| `winRate` | `summary.winRate` (as percentage) |\n| `lossRate` | `summary.byVerdict.lost / summary.total` |\n| `inconclusiveRate` | `summary.byVerdict.inconclusive / summary.total` |\n| `avgDuration` | `summary.avgDurationDays` |\n| `medianDuration` | `summary.medianDurationDays` |\n| `srmFailureRate` | `summary.srmFailureRate` |\n| `guardrailRegressionRate` | `summary.guardrailRegressionRate` |\n\n**B. Monthly Trends**\n- Parse `summary.byMonth` keys (format: `'2025-12'`)\n- Identify **Busiest Month**: month with highest `ended` count\n- Calculate **H1 vs H2 Velocity**: sum of Jan-Jun vs Jul-Dec completions\n\n**C. Project Insights**\n- Join `summary.byProject` with project names from `get_projects`\n- Find: Project with most experiments, project with highest win rate\n- If only one project exists, skip comparative insights\n\n**D. Tag Insights**\n- If `summary.byTag` is empty → skip tag slide\n- Otherwise: most common tag, highest win-rate tag\n\n**E. Winners & Losers**\n| Variable | Source |\n|----------|--------|\n| `biggestWinner` | `topWinners[0]` (highest lift) |\n| `biggestLearning` | `topLosers[0]` (frame as 'Impact Avoided') |\n| `top3Wins` | `topWinners.slice(0, 3)` |\n\n**F. Fun Facts**\n- **Longest experiment:** Max `durationDays` from `experiments[]`\n- **Most users:** Max `totalUsers` from `experiments[]`\n- **Quickest win:** Min `durationDays` where `verdict === 'won'`\n- **SRM catches:** `srmIssues.length` (frame positively: 'GrowthBook protected your data integrity')\n\n---\n\n## Phase 2: Slide Manifest (10 Slides)\n\n| # | Slide | Key Content | Background Gradient |\n|---|-------|-------------|---------------------|\n| 1 | **Intro** | 'Your 2025 Wrapped' + GrowthBook logo | Brand purple→blue→cyan |\n| 2 | **Volume** | Total experiments + total users reached | Brand gradient |\n| 3 | **Win Rate** | Hero metric: X% win rate | Emerald/teal |\n| 4 | **Biggest Winner** | Experiment name + lift + hypothesis | Emerald/teal |\n| 5 | **Biggest Learning** | 'You avoided a -X% impact' | Rose/amber (positive framing) |\n| 6 | **Velocity** | Busiest month + H1 vs H2 comparison | Zinc/slate |\n| 7 | **Rigor** | SRM catches + guardrail saves | Zinc/slate |\n| 8 | **Fun Facts** | 2-3 memorable stats | Brand gradient |\n| 9 | **Summary** | Quick stats grid (4-6 metrics) | Brand gradient |\n| 10 | **Share Card** | Privacy-safe summary for screenshots | Holographic brand gradient |\n\n### Slide Skip Logic\n| Condition | Action |\n|-----------|--------|\n| `total === 0` | Show 'No completed experiments' single slide |\n| `total < 3` | Show simplified 5-slide version |\n| `topWinners.length === 0` | Skip slides 4, 8 |\n| `topLosers.length === 0` | Skip slide 5 |\n| `byTag` is empty | Skip any tag-related content |\n| `srmIssues.length === 0` | Simplify slide 7 |\n\n---\n\n## Phase 3: React Component Architecture\n\n### File Structure\nSingle-file React component with:\n- Framer Motion for animations\n- Tailwind CSS for styling\n- Google Fonts import in `<style>` tag\n\n### Core Components\n```tsx\n// Reusable slide wrapper\n<SlideContainer gradient='from-purple-950 via-indigo-900 to-black'>\n {children}\n</SlideContainer>\n\n// Features:\n// - Full viewport height with safe area padding (notch-friendly)\n// - Vertical centering for hero content\n// - Consistent padding: p-6 md:p-12\n// - Glass card option for content containers\n```\n\n### Loading State\nWhile data is fetching, display:\n- Animated gradient background (subtle movement)\n- Pulsing GrowthBook logo or circular spinner\n- Text: 'Crunching your 2025 experiments...' with fade animation\n- Optional: Fake progress bar (0→90% over 10s, pause until loaded)\n\n### Navigation\n- **Click/Tap:** Right half → next, Left half → previous\n- **Keyboard:** Arrow keys (← →), Spacebar (next)\n- **Swipe:** Support touch swipe gestures\n- **Progress indicator:** Dot navigation at bottom (current slide highlighted)\n\n### Animation Specs (Critical)\n\n**Directional Awareness:**\n```tsx\n// Track direction for enter/exit animations\nconst [[page, direction], setPage] = useState([0, 0]);\n\n// Variants\nconst variants = {\n enter: (dir) => ({ x: dir > 0 ? '100%' : '-100%', opacity: 0 }),\n center: { x: 0, opacity: 1 },\n exit: (dir) => ({ x: dir < 0 ? '100%' : '-100%', opacity: 0 }),\n};\n```\n\n**Physics:**\n```tsx\ntransition: { \n type: 'spring', \n stiffness: 300, \n damping: 30 \n}\n```\n\n**Staggered Entrance (per slide):**\n1. `0ms` — Background/container fades in\n2. `150ms` — Label slides up (`y: 20 → 0`)\n3. `300ms` — Hero metric pops in (`scale: 0.8 → 1`, `opacity: 0 → 1`)\n4. `450ms` — Supporting text/chart fades in\n5. `600ms` — Any secondary elements\n\n**Exit Strategy:**\n- Use `mode='popLayout'` in `<AnimatePresence>`\n- Outgoing slide exits while incoming enters (parallax crossfade)\n\n### Accessibility\n- `prefers-reduced-motion`: Disable spring animations, use simple fades\n- Semantic HTML: Use `<section>` for slides, `<h1>` for hero metrics\n- Screen reader: `aria-live='polite'` for slide changes\n\n---\n\n## Phase 4: The Share Card (Slide 10)\n\n### Purpose\nA screenshot-optimized slide users can share on social media.\n\n### Design Specs\n| Property | Value |\n|----------|-------|\n| Background | Holographic gradient: brand colors with subtle transparency |\n| Padding | 48px safe margins |\n| Contrast | High — white text on dark gradient |\n\n### Content Rules\n\n✅ **INCLUDE:**\n- Total experiments completed\n- Win rate (%)\n- Learning rate (%) — `lost / total`\n- Inconclusive rate (%)\n- Total users reached\n- Year: '2025'\n- Footer: 'Powered by GrowthBook' with SVG logo\n\n❌ **EXCLUDE (Privacy):**\n- Experiment names\n- Project names\n- Hypothesis text\n- Specific lift percentages\n- Owner/user names\n- Any identifiable business data\n\n### Logo Asset\n```svg\n<svg xmlns='http://www.w3.org/2000/svg' width='313' height='50' fill='none'><g fill-rule='evenodd' clip-path='url(#a)' clip-rule='evenodd'><path fill='#fff' d='M54.904 28.479c0 10.154 7.594 16.81 16.93 16.81 5.61 0 10.109-2.331 13.301-5.878V27.264H69.85v5.005h9.626v5.052c-1.45 1.36-4.353 2.915-7.642 2.915-6.385 0-11.125-4.956-11.125-11.758 0-6.802 4.74-11.708 11.125-11.708 1.637-.001 3.25.39 4.706 1.143a10.284 10.284 0 0 1 3.662 3.18l4.643-2.622c-2.612-3.742-6.723-6.754-13.011-6.754-9.336 0-16.93 6.608-16.93 16.762Zm34.531 16.178h5.079V28.673c1.016-1.651 3.87-3.109 5.998-3.109a7.47 7.47 0 0 1 1.596.146v-5.053c-3.048 0-5.853 1.749-7.594 3.984v-3.45h-5.079v23.466Zm25.875.584c7.497 0 12.043-5.588 12.043-12.34 0-6.706-4.546-12.293-12.043-12.293-7.401 0-11.996 5.587-11.996 12.292 0 6.753 4.595 12.34 11.996 12.34Zm0-4.518c-4.305 0-6.724-3.643-6.724-7.822 0-4.13 2.419-7.774 6.724-7.774 4.353 0 6.771 3.644 6.771 7.774 0 4.177-2.418 7.822-6.771 7.822Zm36.079 3.934h5.321l7.255-23.466h-5.272l-2.467 8.527-2.466 8.526-5.563-17.053h-4.45l-5.562 17.053-4.934-17.053h-5.273l7.255 23.466h5.322l5.417-17.198 5.417 17.198Zm21.664.584c2.37 0 3.869-.632 4.788-1.507l-1.209-3.839c-.387.438-1.306.826-2.273.826-1.452 0-2.225-1.165-2.225-2.768V25.66h4.74v-4.47h-4.74V14.78h-5.079v6.413h-3.869v4.47h3.869v13.554c0 3.887 2.08 6.024 5.998 6.024Zm22.779-.584h5.079V28.043c0-4.81-2.515-7.434-7.546-7.434-3.676 0-6.723 1.944-8.271 3.79V12.251h-5.078v32.405h5.078V28.285c1.21-1.603 3.435-3.158 5.998-3.158 2.854 0 4.74 1.117 4.74 4.761v14.77Zm10.347 0h16.301c6.045 0 9.383-3.741 9.383-8.745 0-3.983-2.806-7.433-6.24-7.967 2.999-.632 5.611-3.352 5.611-7.434 0-4.567-3.289-8.26-9.189-8.26h-15.866v32.407Zm5.659-18.996v-8.405h9.046c2.901 0 4.547 1.798 4.547 4.226 0 2.429-1.646 4.178-4.547 4.178l-9.046.001Zm0 13.992v-8.988h9.286c3.241 0 4.934 2.04 4.934 4.47 0 2.818-1.838 4.518-4.934 4.518h-9.286Zm34.627 5.587c7.498 0 12.045-5.587 12.045-12.34 0-6.704-4.547-12.291-12.045-12.291-7.4 0-11.997 5.586-11.997 12.291 0 6.753 4.597 12.34 11.997 12.34Zm0-4.517c-4.304 0-6.723-3.643-6.723-7.822 0-4.13 2.419-7.774 6.723-7.774 4.355 0 6.772 3.644 6.772 7.774 0 4.177-2.417 7.822-6.772 7.822Zm26.261 4.517c7.498 0 12.045-5.587 12.045-12.34 0-6.704-4.547-12.291-12.045-12.291-7.401 0-11.995 5.586-11.995 12.291 0 6.753 4.594 12.34 11.995 12.34Zm0-4.517c-4.305 0-6.724-3.643-6.724-7.822 0-4.13 2.419-7.774 6.724-7.774 4.354 0 6.771 3.644 6.771 7.774 0 4.177-2.417 7.822-6.771 7.822Zm31.194 3.934h6.384l-9.915-12.826 9.723-10.64h-6.287l-10.304 11.32v-20.26h-5.079v32.407h5.079v-6.316l3.24-3.352 7.159 9.667Z'/><path fill='#06B8F4' d='M16.26 17.4 46.626.242s-2.863 2.53-2.737 8.004c.134 5.835 2.737 8.004 2.737 8.004l-3.568-1.633-27.933 10.982s-.678-1.908-.69-3.346c-.028-3.549 1.825-4.851 1.825-4.851Z'/><path fill='#2076FF' d='M9.8 28.619 42.978 16.43s-2.862 2.529-2.736 8.004c.134 5.834 2.736 8.004 2.736 8.004l-4.634-1.792-29.779 5.887s-.577-1.76-.588-3.064c-.028-3.55 1.825-4.851 1.825-4.851Z'/><path fill='#7B45EA' d='m3.346 38.503 35.004-6.307s-2.863 2.53-2.737 8.004c.135 5.835 2.737 8.004 2.737 8.004H3.607s-2.055-1.254-2.086-5.032c-.029-3.55 1.825-4.67 1.825-4.67Z'/></g><defs><clipPath id='a'><path fill='#fff' d='M0 .012h312.5v49.883H0z'/></clipPath></defs></svg>\n```\n\n---\n\n## Phase 5: Date & Number Formatting\n\n### Dates\n| Context | Format | Example |\n|---------|--------|---------|\n| Monthly trends | `'MMMM yyyy'` | 'December 2025' |\n| Specific dates | `'MMM d, yyyy'` | 'Dec 11, 2025' |\n\nUse `Intl.DateTimeFormat('en-US', options)` for consistency.\n\n### Numbers\n| Type | Format | Example |\n|------|--------|---------|\n| Percentages | 0 decimal places | '60%' |\n| Users (large) | Abbreviated with suffix | '45K' or '1.2M' |\n| Users (small) | Comma-separated | '8,500' |\n| Lift | 1 decimal + sign | '+26.1%' |\n| Days | Integer | '23 days' |\n\n---\n\n## Final Checklist\n\nBefore outputting the component, verify:\n\n- [ ] All 10 slides render (or appropriate subset based on data)\n- [ ] Slide navigation works (click, keyboard, swipe)\n- [ ] Animations are smooth and directional\n- [ ] Loading state displays during data fetch\n- [ ] Edge cases handled (empty tags, single project, no winners)\n- [ ] Share card contains NO private data\n- [ ] GrowthBook logo renders correctly on dark background\n- [ ] Fonts load from Google Fonts\n- [ ] Mobile responsive (test at 375px width)\n- [ ] `prefers-reduced-motion` respected",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
}));
|
|
30
|
+
}
|
package/server/tools/defaults.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleResNotOk } from "../utils.js";
|
|
1
|
+
import { handleResNotOk, fetchWithRateLimit, } from "../utils.js";
|
|
2
2
|
import envPaths from "env-paths";
|
|
3
3
|
import { writeFile, readFile, mkdir, unlink } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
@@ -9,7 +9,7 @@ const experimentDefaultsFile = join(experimentDefaultsDir, "experiment-defaults.
|
|
|
9
9
|
const userDefaultsFile = join(experimentDefaultsDir, "user-defaults.json");
|
|
10
10
|
export async function createDefaults(apiKey, baseApiUrl) {
|
|
11
11
|
try {
|
|
12
|
-
const experimentsResponse = await
|
|
12
|
+
const experimentsResponse = await fetchWithRateLimit(`${baseApiUrl}/api/v1/experiments`, {
|
|
13
13
|
headers: {
|
|
14
14
|
Authorization: `Bearer ${apiKey}`,
|
|
15
15
|
},
|
|
@@ -18,7 +18,7 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
18
18
|
const experimentData = await experimentsResponse.json();
|
|
19
19
|
if (experimentData.experiments.length === 0) {
|
|
20
20
|
// No experiments: return assignment query and environments if possible
|
|
21
|
-
const assignmentQueryResponse = await
|
|
21
|
+
const assignmentQueryResponse = await fetchWithRateLimit(`${baseApiUrl}/api/v1/data-sources`, {
|
|
22
22
|
headers: {
|
|
23
23
|
Authorization: `Bearer ${apiKey}`,
|
|
24
24
|
},
|
|
@@ -29,7 +29,7 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
29
29
|
throw new Error("No data source or assignment query found. Experiments require a data source/assignment query. Set these up in the GrowthBook and try again.");
|
|
30
30
|
}
|
|
31
31
|
const assignmentQuery = dataSourceData.dataSources[0].assignmentQueries[0].id;
|
|
32
|
-
const environmentsResponse = await
|
|
32
|
+
const environmentsResponse = await fetchWithRateLimit(`${baseApiUrl}/api/v1/environments`, {
|
|
33
33
|
headers: {
|
|
34
34
|
Authorization: `Bearer ${apiKey}`,
|
|
35
35
|
},
|
|
@@ -53,7 +53,7 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
53
53
|
}
|
|
54
54
|
let experiments = [];
|
|
55
55
|
if (experimentData.hasMore) {
|
|
56
|
-
const mostRecentExperiments = await
|
|
56
|
+
const mostRecentExperiments = await fetchWithRateLimit(`${baseApiUrl}/api/v1/experiments?offset=${experimentData.total -
|
|
57
57
|
Math.min(50, experimentData.count + experimentData.offset)}&limit=${Math.min(50, experimentData.count + experimentData.offset)}`, {
|
|
58
58
|
headers: {
|
|
59
59
|
Authorization: `Bearer ${apiKey}`,
|
|
@@ -112,7 +112,7 @@ export async function createDefaults(apiKey, baseApiUrl) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
// Fetch environments
|
|
115
|
-
const environmentsResponse = await
|
|
115
|
+
const environmentsResponse = await fetchWithRateLimit(`${baseApiUrl}/api/v1/environments`, {
|
|
116
116
|
headers: {
|
|
117
117
|
Authorization: `Bearer ${apiKey}`,
|
|
118
118
|
},
|
|
@@ -179,7 +179,7 @@ export async function getDefaults(apiKey, baseApiUrl) {
|
|
|
179
179
|
// Re-generate auto defaults if expired
|
|
180
180
|
const generatedDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
181
181
|
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
182
|
-
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults
|
|
182
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults));
|
|
183
183
|
autoDefaults = {
|
|
184
184
|
name: generatedDefaults.name,
|
|
185
185
|
hypothesis: generatedDefaults.hypothesis,
|
|
@@ -192,7 +192,7 @@ export async function getDefaults(apiKey, baseApiUrl) {
|
|
|
192
192
|
// Generate new auto defaults
|
|
193
193
|
const generatedDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
194
194
|
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
195
|
-
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults
|
|
195
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedDefaults));
|
|
196
196
|
autoDefaults = {
|
|
197
197
|
name: generatedDefaults.name,
|
|
198
198
|
hypothesis: generatedDefaults.hypothesis,
|
|
@@ -226,7 +226,7 @@ export async function getDefaults(apiKey, baseApiUrl) {
|
|
|
226
226
|
) {
|
|
227
227
|
const generatedExperimentDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
228
228
|
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
229
|
-
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults
|
|
229
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults));
|
|
230
230
|
parsedExperimentDefaults = generatedExperimentDefaults;
|
|
231
231
|
}
|
|
232
232
|
experimentDefaults = parsedExperimentDefaults;
|
|
@@ -236,7 +236,7 @@ export async function getDefaults(apiKey, baseApiUrl) {
|
|
|
236
236
|
// experimentDefaultsFile does not exist, generate new defaults
|
|
237
237
|
const generatedExperimentDefaults = await createDefaults(apiKey, baseApiUrl);
|
|
238
238
|
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
239
|
-
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults
|
|
239
|
+
await writeFile(experimentDefaultsFile, JSON.stringify(generatedExperimentDefaults));
|
|
240
240
|
experimentDefaults = generatedExperimentDefaults;
|
|
241
241
|
}
|
|
242
242
|
else {
|
|
@@ -257,7 +257,7 @@ export async function registerDefaultsTools({ server, baseApiUrl, apiKey, }) {
|
|
|
257
257
|
content: [
|
|
258
258
|
{
|
|
259
259
|
type: "text",
|
|
260
|
-
text: JSON.stringify(defaults
|
|
260
|
+
text: JSON.stringify(defaults),
|
|
261
261
|
},
|
|
262
262
|
],
|
|
263
263
|
};
|
|
@@ -282,7 +282,7 @@ export async function registerDefaultsTools({ server, baseApiUrl, apiKey, }) {
|
|
|
282
282
|
timestamp: new Date().toISOString(),
|
|
283
283
|
};
|
|
284
284
|
await mkdir(experimentDefaultsDir, { recursive: true });
|
|
285
|
-
await writeFile(userDefaultsFile, JSON.stringify(userDefaults
|
|
285
|
+
await writeFile(userDefaultsFile, JSON.stringify(userDefaults));
|
|
286
286
|
return {
|
|
287
287
|
content: [
|
|
288
288
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { handleResNotOk } from "../utils.js";
|
|
1
|
+
import { handleResNotOk, fetchWithRateLimit, } from "../utils.js";
|
|
2
2
|
/**
|
|
3
3
|
* Tool: get_environments
|
|
4
4
|
*/
|
|
@@ -7,7 +7,7 @@ export function registerEnvironmentTools({ server, baseApiUrl, apiKey, }) {
|
|
|
7
7
|
readOnlyHint: true,
|
|
8
8
|
}, async () => {
|
|
9
9
|
try {
|
|
10
|
-
const res = await
|
|
10
|
+
const res = await fetchWithRateLimit(`${baseApiUrl}/api/v1/environments`, {
|
|
11
11
|
headers: {
|
|
12
12
|
Authorization: `Bearer ${apiKey}`,
|
|
13
13
|
"Content-Type": "application/json",
|
|
@@ -16,7 +16,7 @@ export function registerEnvironmentTools({ server, baseApiUrl, apiKey, }) {
|
|
|
16
16
|
await handleResNotOk(res);
|
|
17
17
|
const data = await res.json();
|
|
18
18
|
return {
|
|
19
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|