@growth-loop/mcp-server 0.1.1
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 +159 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +502 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 growth-loop.dev
|
|
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,159 @@
|
|
|
1
|
+
# @growth-loop/mcp-server
|
|
2
|
+
|
|
3
|
+
> The AI growth engineer for vibe-coded SaaS — talks to your Claude.
|
|
4
|
+
|
|
5
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server that connects
|
|
6
|
+
[growth-loop.dev](https://growth-loop.dev) — funnels, retention, peer benchmarks,
|
|
7
|
+
and the AI growth engineer — to **Claude Desktop**, **Claude Code**, and any other
|
|
8
|
+
MCP-compatible client.
|
|
9
|
+
|
|
10
|
+
Once installed, Claude can answer questions like:
|
|
11
|
+
|
|
12
|
+
> "What changed in my MRR last week?"
|
|
13
|
+
> "Why is my signup→paid funnel down?"
|
|
14
|
+
> "How does my D7 retention compare to other B2B SaaS at $1–10k MRR?"
|
|
15
|
+
|
|
16
|
+
…with answers cited to actual rows in your funnel — not a chat full of guesses.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
### Claude Desktop
|
|
23
|
+
|
|
24
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or
|
|
25
|
+
the equivalent on your platform:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"growth-loop": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@growth-loop/mcp-server"],
|
|
33
|
+
"env": {
|
|
34
|
+
"GROWTH_API_KEY": "pk_live_…",
|
|
35
|
+
"GROWTH_HOST": "https://api.growth-loop.dev"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart Claude Desktop. Open a chat, click the slider icon — `growth-loop` tools
|
|
43
|
+
will be listed.
|
|
44
|
+
|
|
45
|
+
### Claude Code
|
|
46
|
+
|
|
47
|
+
From inside your project:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
claude mcp add growth-loop \
|
|
51
|
+
-- npx -y @growth-loop/mcp-server \
|
|
52
|
+
-e GROWTH_API_KEY=pk_live_…
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Auth
|
|
58
|
+
|
|
59
|
+
Two modes — pick one (or both):
|
|
60
|
+
|
|
61
|
+
| Env var | What it unlocks |
|
|
62
|
+
|--------------------------|-------------------------------------------------|
|
|
63
|
+
| `GROWTH_API_KEY` | Read endpoints (overview, funnels, retention, anomalies, commits, benchmarks). Get one at [growth-loop.dev/dashboard/setup](https://growth-loop.dev/dashboard/setup). |
|
|
64
|
+
| `GROWTH_SESSION_TOKEN` | All of the above **+** `growth_engineer_ask` (the headline tool). Copy the value of the `growth_session` cookie from your browser devtools while logged in. |
|
|
65
|
+
|
|
66
|
+
`GROWTH_HOST` defaults to `https://api.growth-loop.dev`. Override for self-host.
|
|
67
|
+
`GROWTH_PROJECT_ID` is optional — set if you want to pin a specific project.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## What it exposes
|
|
72
|
+
|
|
73
|
+
### Tools (12)
|
|
74
|
+
|
|
75
|
+
| Tool | Purpose |
|
|
76
|
+
|-------------------------|--------------------------------------------------------------------------|
|
|
77
|
+
| `overview` | Top-line snapshot: MRR, active users, signup→paid, deltas. |
|
|
78
|
+
| `metric_series` | Time series for any metric. |
|
|
79
|
+
| `funnels_list` | List configured funnels. |
|
|
80
|
+
| `funnel_get` | Conversion + drop-off for a named funnel. |
|
|
81
|
+
| `retention` | D1 / D7 / D30 cohort retention. |
|
|
82
|
+
| `diagnose` | Closed-loop investigation on a metric — ranked hypotheses with citations.|
|
|
83
|
+
| `why_event` | Explain why users drop on / interact with a specific event. |
|
|
84
|
+
| `anomalies_list` | Recent statistical anomalies with candidate hypotheses. |
|
|
85
|
+
| `commits_list` | Git-aware analytics — commits annotated with measured metric impact. |
|
|
86
|
+
| `peer_benchmark` | Compare to anonymized peer SaaS at the same category + stage. |
|
|
87
|
+
| `hypotheses_list` | RICE-ranked hypothesis backlog. |
|
|
88
|
+
| **`growth_engineer_ask`** | **The headline tool. Ask the AI growth engineer anything — grounded in your funnel, retention, peer benchmarks, hypotheses, deploys, and the last 30 raw events.** |
|
|
89
|
+
|
|
90
|
+
### Resources
|
|
91
|
+
|
|
92
|
+
- `growth://overview`
|
|
93
|
+
- `growth://anomalies/recent`
|
|
94
|
+
- `growth://commits/recent`
|
|
95
|
+
- `growth://benchmarks/peers`
|
|
96
|
+
- `growth://hypotheses`
|
|
97
|
+
|
|
98
|
+
### Prompts (slash commands)
|
|
99
|
+
|
|
100
|
+
- `/weekly` — what changed, biggest mover, 3 actions for the week.
|
|
101
|
+
- `/pmf` — score 0–10 + what blocks the next click up.
|
|
102
|
+
- `/pivot` — cohorts outperforming by ≥2× on retention or LTV.
|
|
103
|
+
- `/icp` — who pays the most, retains the longest.
|
|
104
|
+
- `/strategy` — 90-day plan, one focus area.
|
|
105
|
+
- `/diagnose <metric>` — root-cause cross-referencing events + commits.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Example session (Claude Code)
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
> /weekly
|
|
113
|
+
|
|
114
|
+
Last week (Apr 21–27 vs Apr 14–20):
|
|
115
|
+
• MRR: $4,210 → $4,820 (+14.5%) [data_citations: subscription_created × 12]
|
|
116
|
+
• signup→paid: 4.1% → 3.6% (−0.5pp) ← biggest mover
|
|
117
|
+
• D7 retention: 31% (cohort_size=84)
|
|
118
|
+
|
|
119
|
+
Top three actions:
|
|
120
|
+
1. Diagnose the signup→paid drop. Two new commits to /onboarding/step-2
|
|
121
|
+
landed Apr 23 — possible regression. (commits: 8a3f2c1, 9b1d4e2)
|
|
122
|
+
2. Run a peer benchmark — D7=31% is below B2B-SaaS-0_1k_mrr p25=38%.
|
|
123
|
+
3. Write up the +14.5% MRR week as your weekly digest's win.
|
|
124
|
+
|
|
125
|
+
Want me to open a hypothesis for any of these?
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Self-host
|
|
131
|
+
|
|
132
|
+
Source: <https://github.com/growth-loop/growth-loop.dev/tree/main/packages/mcp-server>
|
|
133
|
+
|
|
134
|
+
The MCP server is MIT-licensed and ships as a single Node binary. If you self-host
|
|
135
|
+
[growth-loop.dev](https://growth-loop.dev), point `GROWTH_HOST` at your own
|
|
136
|
+
deployment.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Why this exists
|
|
141
|
+
|
|
142
|
+
We're building **growth-loop.dev**: an AI growth engineer for vibe-coded SaaS.
|
|
143
|
+
Most product analytics tools assume you'll spend a quarter wiring taxonomies and
|
|
144
|
+
weeks pasting CSVs into ChatGPT. We don't.
|
|
145
|
+
|
|
146
|
+
- **Claude Code instruments the code.** One `claude /growth init` and your funnel
|
|
147
|
+
events ship with your next deploy.
|
|
148
|
+
- **The growth engineer reasons about the data.** Persistent threads, citations
|
|
149
|
+
to real rows, weekly digest, hypothesis state machine.
|
|
150
|
+
- **MCP makes Claude itself the interface.** This package is how that works.
|
|
151
|
+
|
|
152
|
+
If you want the platform: <https://growth-loop.dev>. If you want to follow along:
|
|
153
|
+
<https://twitter.com/growthloop_dev>.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
GetPromptRequestSchema,
|
|
9
|
+
ListPromptsRequestSchema,
|
|
10
|
+
ListResourcesRequestSchema,
|
|
11
|
+
ListToolsRequestSchema,
|
|
12
|
+
ReadResourceRequestSchema
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
|
|
15
|
+
// src/tools.ts
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
var RangeEnum = z.enum(["1h", "24h", "7d", "30d", "90d"]);
|
|
18
|
+
var inputs = {
|
|
19
|
+
overview: z.object({ range: RangeEnum.default("30d") }),
|
|
20
|
+
metric_series: z.object({
|
|
21
|
+
metric: z.string(),
|
|
22
|
+
range: RangeEnum.default("30d")
|
|
23
|
+
}),
|
|
24
|
+
funnel_get: z.object({
|
|
25
|
+
name: z.string(),
|
|
26
|
+
range: RangeEnum.default("30d")
|
|
27
|
+
}),
|
|
28
|
+
funnels_list: z.object({}),
|
|
29
|
+
retention: z.object({ range: RangeEnum.default("90d") }),
|
|
30
|
+
diagnose: z.object({
|
|
31
|
+
metric: z.string(),
|
|
32
|
+
since: z.string().default("24h")
|
|
33
|
+
}),
|
|
34
|
+
why_event: z.object({ event: z.string() }),
|
|
35
|
+
anomalies_list: z.object({
|
|
36
|
+
severity: z.enum(["info", "warn", "critical"]).optional()
|
|
37
|
+
}),
|
|
38
|
+
commits_list: z.object({ since: z.string().default("48h") }),
|
|
39
|
+
peer_benchmark: z.object({}),
|
|
40
|
+
hypotheses_list: z.object({}),
|
|
41
|
+
growth_engineer_ask: z.object({
|
|
42
|
+
question: z.string().min(2).max(8e3).describe("Free-form question to the AI growth engineer")
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
var tools = [
|
|
46
|
+
{
|
|
47
|
+
name: "overview",
|
|
48
|
+
description: "Top-line snapshot for a date range: MRR, active users, signup\u2192paid, deltas vs prior period.",
|
|
49
|
+
inputSchema: zodToJson(inputs.overview)
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "metric_series",
|
|
53
|
+
description: "Return a time series for a metric. Supports system + custom event metrics.",
|
|
54
|
+
inputSchema: zodToJson(inputs.metric_series)
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "funnels_list",
|
|
58
|
+
description: "List configured funnels with name + step count.",
|
|
59
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "funnel_get",
|
|
63
|
+
description: "Get a funnel by name with conversion + drop-off per step.",
|
|
64
|
+
inputSchema: zodToJson(inputs.funnel_get)
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "retention",
|
|
68
|
+
description: "Cohort retention curve (D1, D7, D30) over the requested range.",
|
|
69
|
+
inputSchema: zodToJson(inputs.retention)
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "diagnose",
|
|
73
|
+
description: "Closed-loop investigation on a metric. Returns ranked hypotheses with citations to events + commits.",
|
|
74
|
+
inputSchema: zodToJson(inputs.diagnose)
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "why_event",
|
|
78
|
+
description: "Explain why users drop on or interact with a specific event. Cross-references replays + funnel position.",
|
|
79
|
+
inputSchema: zodToJson(inputs.why_event)
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "anomalies_list",
|
|
83
|
+
description: "List recent statistical anomalies with attached candidate hypotheses.",
|
|
84
|
+
inputSchema: zodToJson(inputs.anomalies_list)
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "commits_list",
|
|
88
|
+
description: "Recent commits annotated with their measured metric impact (git-aware analytics).",
|
|
89
|
+
inputSchema: zodToJson(inputs.commits_list)
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "peer_benchmark",
|
|
93
|
+
description: "Compare your project to anonymized peer SaaS at the same category + stage (signup\u2192paid, retention, MRR).",
|
|
94
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "hypotheses_list",
|
|
98
|
+
description: "RICE-ranked hypothesis backlog (open + testing + completed).",
|
|
99
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "growth_engineer_ask",
|
|
103
|
+
description: "Talk to the AI growth engineer for vibe-coded SaaS. The agent is grounded in your funnel, retention, peer benchmarks, hypotheses, deploys, and the last 30 raw events. Every answer cites real rows from your project. Requires a session token (set GROWTH_SESSION_TOKEN env).",
|
|
104
|
+
inputSchema: zodToJson(inputs.growth_engineer_ask)
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
async function callTool(api2, req) {
|
|
108
|
+
const name = req.params.name;
|
|
109
|
+
const args = req.params.arguments ?? {};
|
|
110
|
+
switch (name) {
|
|
111
|
+
case "overview": {
|
|
112
|
+
const input = inputs.overview.parse(args);
|
|
113
|
+
return text(await api2.getOverview(input.range));
|
|
114
|
+
}
|
|
115
|
+
case "metric_series": {
|
|
116
|
+
const input = inputs.metric_series.parse(args);
|
|
117
|
+
return text(await api2.getMetricSeries(input.metric, input.range));
|
|
118
|
+
}
|
|
119
|
+
case "funnels_list": {
|
|
120
|
+
return text(await api2.listFunnels());
|
|
121
|
+
}
|
|
122
|
+
case "funnel_get": {
|
|
123
|
+
const input = inputs.funnel_get.parse(args);
|
|
124
|
+
return text(await api2.getFunnel(input.name, input.range));
|
|
125
|
+
}
|
|
126
|
+
case "retention": {
|
|
127
|
+
const input = inputs.retention.parse(args);
|
|
128
|
+
return text(await api2.getRetention(input.range));
|
|
129
|
+
}
|
|
130
|
+
case "diagnose": {
|
|
131
|
+
const input = inputs.diagnose.parse(args);
|
|
132
|
+
return text(await api2.diagnose(input.metric, input.since));
|
|
133
|
+
}
|
|
134
|
+
case "why_event": {
|
|
135
|
+
const input = inputs.why_event.parse(args);
|
|
136
|
+
return text(await api2.whyEvent(input.event));
|
|
137
|
+
}
|
|
138
|
+
case "anomalies_list": {
|
|
139
|
+
const input = inputs.anomalies_list.parse(args);
|
|
140
|
+
return text(await api2.listAnomalies(input.severity));
|
|
141
|
+
}
|
|
142
|
+
case "commits_list": {
|
|
143
|
+
const input = inputs.commits_list.parse(args);
|
|
144
|
+
return text(await api2.listCommits(input.since));
|
|
145
|
+
}
|
|
146
|
+
case "peer_benchmark": {
|
|
147
|
+
return text(await api2.getPeerBenchmarks());
|
|
148
|
+
}
|
|
149
|
+
case "hypotheses_list": {
|
|
150
|
+
return text(await api2.listHypotheses());
|
|
151
|
+
}
|
|
152
|
+
case "growth_engineer_ask": {
|
|
153
|
+
const input = inputs.growth_engineer_ask.parse(args);
|
|
154
|
+
return text(await api2.growthEngineerAsk(input.question));
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
throw new Error(`unknown tool: ${name}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function text(payload) {
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function zodToJson(schema) {
|
|
166
|
+
const shape = schema.shape;
|
|
167
|
+
const properties = {};
|
|
168
|
+
const required = [];
|
|
169
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
170
|
+
properties[key] = describe(value);
|
|
171
|
+
if (!value.isOptional()) required.push(key);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties,
|
|
176
|
+
required,
|
|
177
|
+
additionalProperties: false
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function describe(schema) {
|
|
181
|
+
if (schema instanceof z.ZodString) {
|
|
182
|
+
const desc = schema.description;
|
|
183
|
+
return desc ? { type: "string", description: desc } : { type: "string" };
|
|
184
|
+
}
|
|
185
|
+
if (schema instanceof z.ZodNumber) return { type: "number" };
|
|
186
|
+
if (schema instanceof z.ZodBoolean) return { type: "boolean" };
|
|
187
|
+
if (schema instanceof z.ZodEnum) return { type: "string", enum: schema.options };
|
|
188
|
+
if (schema instanceof z.ZodOptional) return describe(schema.unwrap());
|
|
189
|
+
if (schema instanceof z.ZodDefault) return describe(schema.removeDefault());
|
|
190
|
+
return { type: "string" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/resources.ts
|
|
194
|
+
var resources = [
|
|
195
|
+
{
|
|
196
|
+
uri: "growth://overview",
|
|
197
|
+
name: "Overview snapshot",
|
|
198
|
+
description: "Top-line numbers (last 30d): MRR, active users, signup\u2192paid, deltas vs prior period.",
|
|
199
|
+
mimeType: "application/json"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
uri: "growth://anomalies/recent",
|
|
203
|
+
name: "Recent anomalies",
|
|
204
|
+
description: "Statistical anomalies detected in the last 24h with attached candidate hypotheses.",
|
|
205
|
+
mimeType: "application/json"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
uri: "growth://commits/recent",
|
|
209
|
+
name: "Recent commits with metric deltas",
|
|
210
|
+
description: "Last 48h of commits annotated with their measured metric impact.",
|
|
211
|
+
mimeType: "application/json"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
uri: "growth://benchmarks/peers",
|
|
215
|
+
name: "Peer benchmarks",
|
|
216
|
+
description: "How your project compares to anonymized peer SaaS at the same category + stage.",
|
|
217
|
+
mimeType: "application/json"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
uri: "growth://hypotheses",
|
|
221
|
+
name: "Hypothesis backlog",
|
|
222
|
+
description: "Open + testing + completed hypotheses, RICE-ranked.",
|
|
223
|
+
mimeType: "application/json"
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
async function readResource(api2, req) {
|
|
227
|
+
const { uri } = req.params;
|
|
228
|
+
if (uri === "growth://overview") return jsonResource(uri, await api2.getOverview("30d"));
|
|
229
|
+
if (uri === "growth://anomalies/recent") return jsonResource(uri, await api2.listAnomalies());
|
|
230
|
+
if (uri === "growth://commits/recent") return jsonResource(uri, await api2.listCommits("48h"));
|
|
231
|
+
if (uri === "growth://benchmarks/peers") return jsonResource(uri, await api2.getPeerBenchmarks());
|
|
232
|
+
if (uri === "growth://hypotheses") return jsonResource(uri, await api2.listHypotheses());
|
|
233
|
+
throw new Error(`unknown resource: ${uri}`);
|
|
234
|
+
}
|
|
235
|
+
function jsonResource(uri, data) {
|
|
236
|
+
return {
|
|
237
|
+
contents: [
|
|
238
|
+
{
|
|
239
|
+
uri,
|
|
240
|
+
mimeType: "application/json",
|
|
241
|
+
text: JSON.stringify(data, null, 2)
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/prompts.ts
|
|
248
|
+
var prompts = [
|
|
249
|
+
{
|
|
250
|
+
name: "weekly",
|
|
251
|
+
description: "Weekly review \u2014 what changed in the last 7 days, biggest mover, 3 actions to take this week."
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "pmf",
|
|
255
|
+
description: "PMF check \u2014 score 0-10 from retention flatness, organic share, revenue concentration. What blocks the next click up?"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "pivot",
|
|
259
|
+
description: "Pivot analysis \u2014 which behavioral cohorts are outperforming by \u22652\xD7 on retention or LTV, ranked."
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "icp",
|
|
263
|
+
description: "Ideal customer profile \u2014 cross behavioral \xD7 revenue cohorts. Who pays the most, retains the longest?"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "strategy",
|
|
267
|
+
description: "90-day plan \u2014 pick ONE highest-leverage focus area, justify with current metrics + backlog hypotheses."
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "diagnose",
|
|
271
|
+
description: "Why a metric dropped \u2014 root-cause analysis cross-referencing events, funnel position, and recent commits.",
|
|
272
|
+
arguments: [
|
|
273
|
+
{
|
|
274
|
+
name: "metric",
|
|
275
|
+
description: "The metric that dropped (e.g. signup_to_paid, d7_retention, mrr).",
|
|
276
|
+
required: true
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "init",
|
|
282
|
+
description: "Auto-instrument this repo with growth-loop.dev SDK \u2014 detect stack, install SDK, wrap signup/checkout/key call sites, open a single PR."
|
|
283
|
+
}
|
|
284
|
+
];
|
|
285
|
+
var QUESTIONS = {
|
|
286
|
+
weekly: () => "Give me a weekly review. What changed in the last 7 days vs the prior 7? What's the single biggest mover, and what 3 actions should I take this week?",
|
|
287
|
+
pmf: () => "Where am I on PMF? Use retention curve flatness, organic acquisition share, and revenue concentration. Score 0-10 and tell me what's blocking the next click up.",
|
|
288
|
+
pivot: () => "Should I pivot? Look at cohort patterns. Are there segments outperforming by \u22652\xD7 on retention or LTV? Propose 3-5 plausible pivots ranked by data evidence.",
|
|
289
|
+
icp: () => "Who is my ideal customer? Cross behavioral cohorts \xD7 revenue cohorts: who pays the most, retains the longest, refers the most. Output ICP attributes with numbers.",
|
|
290
|
+
strategy: () => "What's my highest-leverage focus for the next 90 days? Pick ONE area and justify it with my current metrics + my backlog hypotheses + memory of prior decisions.",
|
|
291
|
+
diagnose: (args) => `Why has ${args.metric ?? "this metric"} dropped recently? Cross-reference funnel data, raw events, and recent commits. Return ranked hypotheses with confidence scores.`,
|
|
292
|
+
init: () => `Instrument this repo with growth-loop.dev. Read the codebase, install the SDK, wrap the highest-signal call sites with growth helpers, and open a single PR with all changes.
|
|
293
|
+
|
|
294
|
+
## Step 1 \u2014 detect the stack
|
|
295
|
+
|
|
296
|
+
Read package.json, pyproject.toml, framework configs. Pick the SDK:
|
|
297
|
+
- JS/TS (Next, Vite, Express, etc.) \u2192 @growth-loop/sdk
|
|
298
|
+
- Python (FastAPI, Flask) \u2192 growth-loop-sdk
|
|
299
|
+
|
|
300
|
+
If you see @supabase/supabase-js in package.json, the canonical signup/login signal is the onAuthStateChange callback \u2014 wrap that, do NOT chase a custom /api/auth/signup route.
|
|
301
|
+
|
|
302
|
+
## Step 2 \u2014 install one client module
|
|
303
|
+
|
|
304
|
+
Create exactly ONE shared client. Pull GROWTH_KEY from env.
|
|
305
|
+
|
|
306
|
+
TypeScript browser (src/lib/growth.ts):
|
|
307
|
+
\`\`\`ts
|
|
308
|
+
'use client';
|
|
309
|
+
import { instrument } from '@growth-loop/sdk';
|
|
310
|
+
import { createBrowserClient } from '@growth-loop/sdk/browser';
|
|
311
|
+
export const growth = instrument(createBrowserClient({
|
|
312
|
+
apiKey: process.env.NEXT_PUBLIC_GROWTH_KEY!,
|
|
313
|
+
host: process.env.NEXT_PUBLIC_GROWTH_HOST ?? 'https://api.growth-loop.dev',
|
|
314
|
+
}));
|
|
315
|
+
\`\`\`
|
|
316
|
+
|
|
317
|
+
TypeScript server (src/lib/growth.server.ts):
|
|
318
|
+
\`\`\`ts
|
|
319
|
+
import { instrument } from '@growth-loop/sdk';
|
|
320
|
+
import { createServerClient } from '@growth-loop/sdk/node';
|
|
321
|
+
export const growth = instrument(createServerClient({
|
|
322
|
+
apiKey: process.env.GROWTH_KEY!,
|
|
323
|
+
host: process.env.GROWTH_HOST ?? 'https://api.growth-loop.dev',
|
|
324
|
+
environment: process.env.NODE_ENV as 'production' | 'development' | 'preview',
|
|
325
|
+
release: process.env.GIT_COMMIT_SHA,
|
|
326
|
+
}));
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
Python (growth_setup.py):
|
|
330
|
+
\`\`\`python
|
|
331
|
+
import os
|
|
332
|
+
from growth import Growth, GrowthOptions
|
|
333
|
+
growth = Growth(GrowthOptions(
|
|
334
|
+
api_key=os.environ["GROWTH_KEY"],
|
|
335
|
+
host=os.environ.get("GROWTH_HOST", "https://api.growth-loop.dev"),
|
|
336
|
+
))
|
|
337
|
+
\`\`\`
|
|
338
|
+
|
|
339
|
+
## Step 3 \u2014 wrap the highest-signal call sites
|
|
340
|
+
|
|
341
|
+
Aim for 15\u201330 events total. Priority order:
|
|
342
|
+
|
|
343
|
+
1. **Auth** \u2014 wrap signup and login callbacks. After auth: \`growth.identify({ distinctId: user.id })\` then \`growth.step('signup_completed' | 'login_completed')\`.
|
|
344
|
+
2. **Activation funnel** \u2014 one event per step the user must clear to "get value": \`onboarding.started\`, \`first_<thing>_created\`. Use \`growth.step('activation.<step>')\`.
|
|
345
|
+
3. **Revenue** \u2014 at Stripe Checkout entry: \`checkout_started\`. At webhook handler: \`subscription_created\`. At /pricing visit: \`pricing_viewed\`.
|
|
346
|
+
4. **Top 5\u201310 buttons that matter** \u2014 wrap with \`growth.button('<event>', onClick, { location })\`. SKIP nav links, modal close, tooltips.
|
|
347
|
+
5. **Critical async flows (>500ms)** \u2014 wrap with \`growth.span('<name>', fn)\`. Use on payment processing, AI calls, file uploads.
|
|
348
|
+
|
|
349
|
+
Python decorator pattern:
|
|
350
|
+
\`\`\`python
|
|
351
|
+
from growth import growth_span
|
|
352
|
+
from growth_setup import growth
|
|
353
|
+
|
|
354
|
+
@growth_span(growth, "checkout", distinct_id=lambda body: body.user_id)
|
|
355
|
+
async def checkout(body: CheckoutBody): ...
|
|
356
|
+
\`\`\`
|
|
357
|
+
|
|
358
|
+
## Step 4 \u2014 env vars
|
|
359
|
+
|
|
360
|
+
Append to .env.example, prompt user to copy to .env.local:
|
|
361
|
+
\`\`\`
|
|
362
|
+
GROWTH_KEY=pk_live_\u2026
|
|
363
|
+
GROWTH_HOST=https://api.growth-loop.dev
|
|
364
|
+
NEXT_PUBLIC_GROWTH_KEY=pk_live_\u2026 # only for browser SDK
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
## Step 5 \u2014 open a SINGLE PR
|
|
368
|
+
|
|
369
|
+
One commit, one branch (\`feature/growth-init-instrumentation\`), one PR. Description should list each file modified and each event added inline (e.g. \`signup_completed (auth/sign-up.tsx:42)\`).
|
|
370
|
+
|
|
371
|
+
## Guardrails \u2014 do not violate
|
|
372
|
+
|
|
373
|
+
- Never invent events. Only wrap call sites that already exist.
|
|
374
|
+
- 15\u201330 events max in the first PR. More can be added later.
|
|
375
|
+
- NEVER log PII as event properties (email, password, payment details). Use \`distinctId\` for user identity; never put email in \`properties\`.
|
|
376
|
+
- Never log inside tight loops.
|
|
377
|
+
- Don't mix instrumentation with feature work \u2014 separate PR.
|
|
378
|
+
- If the SDK is already installed, don't reinstall \u2014 just add the missing wraps.
|
|
379
|
+
|
|
380
|
+
Begin by reading package.json or pyproject.toml.`
|
|
381
|
+
};
|
|
382
|
+
function getPrompt(req) {
|
|
383
|
+
const name = req.params.name;
|
|
384
|
+
const builder = QUESTIONS[name];
|
|
385
|
+
if (!builder) throw new Error(`unknown prompt: ${name}`);
|
|
386
|
+
const question = builder(req.params.arguments ?? {});
|
|
387
|
+
return {
|
|
388
|
+
description: prompts.find((p) => p.name === name)?.description ?? "",
|
|
389
|
+
messages: [
|
|
390
|
+
{
|
|
391
|
+
role: "user",
|
|
392
|
+
content: { type: "text", text: question }
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/api.ts
|
|
399
|
+
var GrowthApi = class {
|
|
400
|
+
constructor(options) {
|
|
401
|
+
this.options = options;
|
|
402
|
+
}
|
|
403
|
+
options;
|
|
404
|
+
// ─── read endpoints ───────────────────────────────────────────────────
|
|
405
|
+
async getOverview(range) {
|
|
406
|
+
return this.get(`/v1/overview?range=${encodeURIComponent(range)}`);
|
|
407
|
+
}
|
|
408
|
+
async getMetricSeries(metric, range) {
|
|
409
|
+
return this.get(
|
|
410
|
+
`/v1/metrics/series?metric=${encodeURIComponent(metric)}&range=${encodeURIComponent(range)}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
async getFunnel(name, range) {
|
|
414
|
+
return this.get(`/v1/funnels/${encodeURIComponent(name)}?range=${encodeURIComponent(range)}`);
|
|
415
|
+
}
|
|
416
|
+
async listFunnels() {
|
|
417
|
+
return this.get("/v1/funnels");
|
|
418
|
+
}
|
|
419
|
+
async getRetention(range) {
|
|
420
|
+
return this.get(`/v1/retention?range=${encodeURIComponent(range)}`);
|
|
421
|
+
}
|
|
422
|
+
async listAnomalies(severity) {
|
|
423
|
+
const qs = severity ? `?severity=${encodeURIComponent(severity)}` : "";
|
|
424
|
+
return this.get(`/v1/anomalies${qs}`);
|
|
425
|
+
}
|
|
426
|
+
async listCommits(since) {
|
|
427
|
+
return this.get(`/v1/commits?since=${encodeURIComponent(since)}`);
|
|
428
|
+
}
|
|
429
|
+
async getPeerBenchmarks() {
|
|
430
|
+
return this.get("/v1/benchmarks/peers");
|
|
431
|
+
}
|
|
432
|
+
async listHypotheses() {
|
|
433
|
+
return this.get("/v1/hypotheses");
|
|
434
|
+
}
|
|
435
|
+
// ─── agent endpoints ──────────────────────────────────────────────────
|
|
436
|
+
async diagnose(metric, since) {
|
|
437
|
+
return this.post("/v1/agent/diagnose", { metric, since });
|
|
438
|
+
}
|
|
439
|
+
async whyEvent(event) {
|
|
440
|
+
return this.post("/v1/agent/why_event", { event });
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Talk to the AI growth engineer. Creates an ephemeral thread, posts the
|
|
444
|
+
* question, returns the assistant reply with citations. Requires a
|
|
445
|
+
* session token (cookie auth) — api-keys don't reach this endpoint.
|
|
446
|
+
*/
|
|
447
|
+
async growthEngineerAsk(content) {
|
|
448
|
+
const created = await this.post("/v1/growth-engineer/threads", {});
|
|
449
|
+
return this.post(`/v1/growth-engineer/threads/${created.id}/messages`, { content });
|
|
450
|
+
}
|
|
451
|
+
// ─── transport ────────────────────────────────────────────────────────
|
|
452
|
+
async get(path) {
|
|
453
|
+
return this.request("GET", path);
|
|
454
|
+
}
|
|
455
|
+
async post(path, body) {
|
|
456
|
+
return this.request("POST", path, body);
|
|
457
|
+
}
|
|
458
|
+
async request(method, path, body) {
|
|
459
|
+
const headers = {
|
|
460
|
+
"content-type": "application/json"
|
|
461
|
+
};
|
|
462
|
+
if (this.options.apiKey) headers["x-growth-api-key"] = this.options.apiKey;
|
|
463
|
+
if (this.options.sessionToken) headers["cookie"] = `growth_session=${this.options.sessionToken}`;
|
|
464
|
+
if (this.options.projectId) headers["x-growth-project"] = this.options.projectId;
|
|
465
|
+
const res = await fetch(`${this.options.host}${path}`, {
|
|
466
|
+
method,
|
|
467
|
+
headers,
|
|
468
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
469
|
+
});
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
const text2 = await res.text().catch(() => "");
|
|
472
|
+
throw new Error(`growth-api ${res.status} ${path}: ${text2}`);
|
|
473
|
+
}
|
|
474
|
+
return res.json();
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/index.ts
|
|
479
|
+
var apiKey = process.env.GROWTH_API_KEY;
|
|
480
|
+
var sessionToken = process.env.GROWTH_SESSION_TOKEN;
|
|
481
|
+
var host = process.env.GROWTH_HOST ?? "https://api.growth-loop.dev";
|
|
482
|
+
var projectId = process.env.GROWTH_PROJECT_ID;
|
|
483
|
+
if (!apiKey && !sessionToken) {
|
|
484
|
+
process.stderr.write(
|
|
485
|
+
"[growth-mcp] either GROWTH_API_KEY or GROWTH_SESSION_TOKEN is required\n api-key works for ingest + read endpoints; session-token unlocks growth_engineer_ask.\n"
|
|
486
|
+
);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
var api = new GrowthApi({ apiKey, sessionToken, host, projectId });
|
|
490
|
+
var server = new Server(
|
|
491
|
+
{ name: "growth-loop", version: "0.1.0" },
|
|
492
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
|
|
493
|
+
);
|
|
494
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
495
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => callTool(api, req));
|
|
496
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources }));
|
|
497
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => readResource(api, req));
|
|
498
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts }));
|
|
499
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => getPrompt(req));
|
|
500
|
+
var transport = new StdioServerTransport();
|
|
501
|
+
await server.connect(transport);
|
|
502
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tools.ts","../src/resources.ts","../src/prompts.ts","../src/api.ts"],"sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { tools, callTool } from './tools.js';\nimport { resources, readResource } from './resources.js';\nimport { prompts, getPrompt } from './prompts.js';\nimport { GrowthApi } from './api.js';\n\nconst apiKey = process.env.GROWTH_API_KEY;\nconst sessionToken = process.env.GROWTH_SESSION_TOKEN;\nconst host = process.env.GROWTH_HOST ?? 'https://api.growth-loop.dev';\nconst projectId = process.env.GROWTH_PROJECT_ID;\n\nif (!apiKey && !sessionToken) {\n process.stderr.write(\n '[growth-mcp] either GROWTH_API_KEY or GROWTH_SESSION_TOKEN is required\\n' +\n ' api-key works for ingest + read endpoints; session-token unlocks growth_engineer_ask.\\n',\n );\n process.exit(1);\n}\n\nconst api = new GrowthApi({ apiKey, sessionToken, host, projectId });\n\nconst server = new Server(\n { name: 'growth-loop', version: '0.1.0' },\n { capabilities: { tools: {}, resources: {}, prompts: {} } },\n);\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));\nserver.setRequestHandler(CallToolRequestSchema, async (req) => callTool(api, req));\n\nserver.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources }));\nserver.setRequestHandler(ReadResourceRequestSchema, async (req) => readResource(api, req));\n\nserver.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts }));\nserver.setRequestHandler(GetPromptRequestSchema, async (req) => getPrompt(req));\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","import type {\n CallToolRequest,\n CallToolResult,\n Tool,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport type { GrowthApi } from './api.js';\n\nconst RangeEnum = z.enum(['1h', '24h', '7d', '30d', '90d']);\n\nconst inputs = {\n overview: z.object({ range: RangeEnum.default('30d') }),\n metric_series: z.object({\n metric: z.string(),\n range: RangeEnum.default('30d'),\n }),\n funnel_get: z.object({\n name: z.string(),\n range: RangeEnum.default('30d'),\n }),\n funnels_list: z.object({}),\n retention: z.object({ range: RangeEnum.default('90d') }),\n diagnose: z.object({\n metric: z.string(),\n since: z.string().default('24h'),\n }),\n why_event: z.object({ event: z.string() }),\n anomalies_list: z.object({\n severity: z.enum(['info', 'warn', 'critical']).optional(),\n }),\n commits_list: z.object({ since: z.string().default('48h') }),\n peer_benchmark: z.object({}),\n hypotheses_list: z.object({}),\n growth_engineer_ask: z.object({\n question: z\n .string()\n .min(2)\n .max(8000)\n .describe('Free-form question to the AI growth engineer'),\n }),\n};\n\nexport const tools: Tool[] = [\n {\n name: 'overview',\n description:\n 'Top-line snapshot for a date range: MRR, active users, signup→paid, deltas vs prior period.',\n inputSchema: zodToJson(inputs.overview),\n },\n {\n name: 'metric_series',\n description: 'Return a time series for a metric. Supports system + custom event metrics.',\n inputSchema: zodToJson(inputs.metric_series),\n },\n {\n name: 'funnels_list',\n description: 'List configured funnels with name + step count.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n },\n {\n name: 'funnel_get',\n description: 'Get a funnel by name with conversion + drop-off per step.',\n inputSchema: zodToJson(inputs.funnel_get),\n },\n {\n name: 'retention',\n description: 'Cohort retention curve (D1, D7, D30) over the requested range.',\n inputSchema: zodToJson(inputs.retention),\n },\n {\n name: 'diagnose',\n description:\n 'Closed-loop investigation on a metric. Returns ranked hypotheses with citations to events + commits.',\n inputSchema: zodToJson(inputs.diagnose),\n },\n {\n name: 'why_event',\n description:\n 'Explain why users drop on or interact with a specific event. Cross-references replays + funnel position.',\n inputSchema: zodToJson(inputs.why_event),\n },\n {\n name: 'anomalies_list',\n description: 'List recent statistical anomalies with attached candidate hypotheses.',\n inputSchema: zodToJson(inputs.anomalies_list),\n },\n {\n name: 'commits_list',\n description: 'Recent commits annotated with their measured metric impact (git-aware analytics).',\n inputSchema: zodToJson(inputs.commits_list),\n },\n {\n name: 'peer_benchmark',\n description:\n 'Compare your project to anonymized peer SaaS at the same category + stage (signup→paid, retention, MRR).',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n },\n {\n name: 'hypotheses_list',\n description: 'RICE-ranked hypothesis backlog (open + testing + completed).',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n },\n {\n name: 'growth_engineer_ask',\n description:\n 'Talk to the AI growth engineer for vibe-coded SaaS. The agent is grounded in your funnel, retention, peer benchmarks, hypotheses, deploys, and the last 30 raw events. Every answer cites real rows from your project. Requires a session token (set GROWTH_SESSION_TOKEN env).',\n inputSchema: zodToJson(inputs.growth_engineer_ask),\n },\n];\n\nexport async function callTool(api: GrowthApi, req: CallToolRequest): Promise<CallToolResult> {\n const name = req.params.name;\n const args = req.params.arguments ?? {};\n\n switch (name) {\n case 'overview': {\n const input = inputs.overview.parse(args);\n return text(await api.getOverview(input.range));\n }\n case 'metric_series': {\n const input = inputs.metric_series.parse(args);\n return text(await api.getMetricSeries(input.metric, input.range));\n }\n case 'funnels_list': {\n return text(await api.listFunnels());\n }\n case 'funnel_get': {\n const input = inputs.funnel_get.parse(args);\n return text(await api.getFunnel(input.name, input.range));\n }\n case 'retention': {\n const input = inputs.retention.parse(args);\n return text(await api.getRetention(input.range));\n }\n case 'diagnose': {\n const input = inputs.diagnose.parse(args);\n return text(await api.diagnose(input.metric, input.since));\n }\n case 'why_event': {\n const input = inputs.why_event.parse(args);\n return text(await api.whyEvent(input.event));\n }\n case 'anomalies_list': {\n const input = inputs.anomalies_list.parse(args);\n return text(await api.listAnomalies(input.severity));\n }\n case 'commits_list': {\n const input = inputs.commits_list.parse(args);\n return text(await api.listCommits(input.since));\n }\n case 'peer_benchmark': {\n return text(await api.getPeerBenchmarks());\n }\n case 'hypotheses_list': {\n return text(await api.listHypotheses());\n }\n case 'growth_engineer_ask': {\n const input = inputs.growth_engineer_ask.parse(args);\n return text(await api.growthEngineerAsk(input.question));\n }\n default:\n throw new Error(`unknown tool: ${name}`);\n }\n}\n\nfunction text(payload: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],\n };\n}\n\nfunction zodToJson(schema: z.ZodTypeAny): Tool['inputSchema'] {\n const shape = (schema as z.ZodObject<Record<string, z.ZodTypeAny>>).shape;\n const properties: Record<string, object> = {};\n const required: string[] = [];\n for (const [key, value] of Object.entries(shape)) {\n properties[key] = describe(value);\n if (!value.isOptional()) required.push(key);\n }\n return {\n type: 'object',\n properties,\n required,\n additionalProperties: false,\n };\n}\n\nfunction describe(schema: z.ZodTypeAny): Record<string, unknown> {\n if (schema instanceof z.ZodString) {\n const desc = schema.description;\n return desc ? { type: 'string', description: desc } : { type: 'string' };\n }\n if (schema instanceof z.ZodNumber) return { type: 'number' };\n if (schema instanceof z.ZodBoolean) return { type: 'boolean' };\n if (schema instanceof z.ZodEnum) return { type: 'string', enum: schema.options };\n if (schema instanceof z.ZodOptional) return describe(schema.unwrap());\n if (schema instanceof z.ZodDefault) return describe(schema.removeDefault());\n return { type: 'string' };\n}\n","import type {\n ReadResourceRequest,\n ReadResourceResult,\n Resource,\n} from '@modelcontextprotocol/sdk/types.js';\nimport type { GrowthApi } from './api.js';\n\nexport const resources: Resource[] = [\n {\n uri: 'growth://overview',\n name: 'Overview snapshot',\n description: 'Top-line numbers (last 30d): MRR, active users, signup→paid, deltas vs prior period.',\n mimeType: 'application/json',\n },\n {\n uri: 'growth://anomalies/recent',\n name: 'Recent anomalies',\n description: 'Statistical anomalies detected in the last 24h with attached candidate hypotheses.',\n mimeType: 'application/json',\n },\n {\n uri: 'growth://commits/recent',\n name: 'Recent commits with metric deltas',\n description: 'Last 48h of commits annotated with their measured metric impact.',\n mimeType: 'application/json',\n },\n {\n uri: 'growth://benchmarks/peers',\n name: 'Peer benchmarks',\n description: 'How your project compares to anonymized peer SaaS at the same category + stage.',\n mimeType: 'application/json',\n },\n {\n uri: 'growth://hypotheses',\n name: 'Hypothesis backlog',\n description: 'Open + testing + completed hypotheses, RICE-ranked.',\n mimeType: 'application/json',\n },\n];\n\nexport async function readResource(\n api: GrowthApi,\n req: ReadResourceRequest,\n): Promise<ReadResourceResult> {\n const { uri } = req.params;\n\n if (uri === 'growth://overview') return jsonResource(uri, await api.getOverview('30d'));\n if (uri === 'growth://anomalies/recent') return jsonResource(uri, await api.listAnomalies());\n if (uri === 'growth://commits/recent') return jsonResource(uri, await api.listCommits('48h'));\n if (uri === 'growth://benchmarks/peers') return jsonResource(uri, await api.getPeerBenchmarks());\n if (uri === 'growth://hypotheses') return jsonResource(uri, await api.listHypotheses());\n\n throw new Error(`unknown resource: ${uri}`);\n}\n\nfunction jsonResource(uri: string, data: unknown): ReadResourceResult {\n return {\n contents: [\n {\n uri,\n mimeType: 'application/json',\n text: JSON.stringify(data, null, 2),\n },\n ],\n };\n}\n","import type {\n GetPromptRequest,\n GetPromptResult,\n Prompt,\n} from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * MCP prompts surface as slash-commands in Claude Desktop / Claude Code.\n * Each prompt is a pre-baked question for the AI growth engineer that the\n * model will run via the `growth_engineer_ask` tool — saving the user from\n * remembering the framework.\n */\nexport const prompts: Prompt[] = [\n {\n name: 'weekly',\n description:\n 'Weekly review — what changed in the last 7 days, biggest mover, 3 actions to take this week.',\n },\n {\n name: 'pmf',\n description:\n 'PMF check — score 0-10 from retention flatness, organic share, revenue concentration. What blocks the next click up?',\n },\n {\n name: 'pivot',\n description:\n 'Pivot analysis — which behavioral cohorts are outperforming by ≥2× on retention or LTV, ranked.',\n },\n {\n name: 'icp',\n description:\n 'Ideal customer profile — cross behavioral × revenue cohorts. Who pays the most, retains the longest?',\n },\n {\n name: 'strategy',\n description:\n \"90-day plan — pick ONE highest-leverage focus area, justify with current metrics + backlog hypotheses.\",\n },\n {\n name: 'diagnose',\n description:\n 'Why a metric dropped — root-cause analysis cross-referencing events, funnel position, and recent commits.',\n arguments: [\n {\n name: 'metric',\n description: 'The metric that dropped (e.g. signup_to_paid, d7_retention, mrr).',\n required: true,\n },\n ],\n },\n {\n name: 'init',\n description:\n 'Auto-instrument this repo with growth-loop.dev SDK — detect stack, install SDK, wrap signup/checkout/key call sites, open a single PR.',\n },\n];\n\nconst QUESTIONS: Record<string, (args: Record<string, string>) => string> = {\n weekly: () =>\n \"Give me a weekly review. What changed in the last 7 days vs the prior 7? What's the single biggest mover, and what 3 actions should I take this week?\",\n pmf: () =>\n \"Where am I on PMF? Use retention curve flatness, organic acquisition share, and revenue concentration. Score 0-10 and tell me what's blocking the next click up.\",\n pivot: () =>\n 'Should I pivot? Look at cohort patterns. Are there segments outperforming by ≥2× on retention or LTV? Propose 3-5 plausible pivots ranked by data evidence.',\n icp: () =>\n 'Who is my ideal customer? Cross behavioral cohorts × revenue cohorts: who pays the most, retains the longest, refers the most. Output ICP attributes with numbers.',\n strategy: () =>\n \"What's my highest-leverage focus for the next 90 days? Pick ONE area and justify it with my current metrics + my backlog hypotheses + memory of prior decisions.\",\n diagnose: (args) =>\n `Why has ${args.metric ?? 'this metric'} dropped recently? Cross-reference funnel data, raw events, and recent commits. Return ranked hypotheses with confidence scores.`,\n init: () => `Instrument this repo with growth-loop.dev. Read the codebase, install the SDK, wrap the highest-signal call sites with growth helpers, and open a single PR with all changes.\n\n## Step 1 — detect the stack\n\nRead package.json, pyproject.toml, framework configs. Pick the SDK:\n- JS/TS (Next, Vite, Express, etc.) → @growth-loop/sdk\n- Python (FastAPI, Flask) → growth-loop-sdk\n\nIf you see @supabase/supabase-js in package.json, the canonical signup/login signal is the onAuthStateChange callback — wrap that, do NOT chase a custom /api/auth/signup route.\n\n## Step 2 — install one client module\n\nCreate exactly ONE shared client. Pull GROWTH_KEY from env.\n\nTypeScript browser (src/lib/growth.ts):\n\\`\\`\\`ts\n'use client';\nimport { instrument } from '@growth-loop/sdk';\nimport { createBrowserClient } from '@growth-loop/sdk/browser';\nexport const growth = instrument(createBrowserClient({\n apiKey: process.env.NEXT_PUBLIC_GROWTH_KEY!,\n host: process.env.NEXT_PUBLIC_GROWTH_HOST ?? 'https://api.growth-loop.dev',\n}));\n\\`\\`\\`\n\nTypeScript server (src/lib/growth.server.ts):\n\\`\\`\\`ts\nimport { instrument } from '@growth-loop/sdk';\nimport { createServerClient } from '@growth-loop/sdk/node';\nexport const growth = instrument(createServerClient({\n apiKey: process.env.GROWTH_KEY!,\n host: process.env.GROWTH_HOST ?? 'https://api.growth-loop.dev',\n environment: process.env.NODE_ENV as 'production' | 'development' | 'preview',\n release: process.env.GIT_COMMIT_SHA,\n}));\n\\`\\`\\`\n\nPython (growth_setup.py):\n\\`\\`\\`python\nimport os\nfrom growth import Growth, GrowthOptions\ngrowth = Growth(GrowthOptions(\n api_key=os.environ[\"GROWTH_KEY\"],\n host=os.environ.get(\"GROWTH_HOST\", \"https://api.growth-loop.dev\"),\n))\n\\`\\`\\`\n\n## Step 3 — wrap the highest-signal call sites\n\nAim for 15–30 events total. Priority order:\n\n1. **Auth** — wrap signup and login callbacks. After auth: \\`growth.identify({ distinctId: user.id })\\` then \\`growth.step('signup_completed' | 'login_completed')\\`.\n2. **Activation funnel** — one event per step the user must clear to \"get value\": \\`onboarding.started\\`, \\`first_<thing>_created\\`. Use \\`growth.step('activation.<step>')\\`.\n3. **Revenue** — at Stripe Checkout entry: \\`checkout_started\\`. At webhook handler: \\`subscription_created\\`. At /pricing visit: \\`pricing_viewed\\`.\n4. **Top 5–10 buttons that matter** — wrap with \\`growth.button('<event>', onClick, { location })\\`. SKIP nav links, modal close, tooltips.\n5. **Critical async flows (>500ms)** — wrap with \\`growth.span('<name>', fn)\\`. Use on payment processing, AI calls, file uploads.\n\nPython decorator pattern:\n\\`\\`\\`python\nfrom growth import growth_span\nfrom growth_setup import growth\n\n@growth_span(growth, \"checkout\", distinct_id=lambda body: body.user_id)\nasync def checkout(body: CheckoutBody): ...\n\\`\\`\\`\n\n## Step 4 — env vars\n\nAppend to .env.example, prompt user to copy to .env.local:\n\\`\\`\\`\nGROWTH_KEY=pk_live_…\nGROWTH_HOST=https://api.growth-loop.dev\nNEXT_PUBLIC_GROWTH_KEY=pk_live_… # only for browser SDK\n\\`\\`\\`\n\n## Step 5 — open a SINGLE PR\n\nOne commit, one branch (\\`feature/growth-init-instrumentation\\`), one PR. Description should list each file modified and each event added inline (e.g. \\`signup_completed (auth/sign-up.tsx:42)\\`).\n\n## Guardrails — do not violate\n\n- Never invent events. Only wrap call sites that already exist.\n- 15–30 events max in the first PR. More can be added later.\n- NEVER log PII as event properties (email, password, payment details). Use \\`distinctId\\` for user identity; never put email in \\`properties\\`.\n- Never log inside tight loops.\n- Don't mix instrumentation with feature work — separate PR.\n- If the SDK is already installed, don't reinstall — just add the missing wraps.\n\nBegin by reading package.json or pyproject.toml.`,\n};\n\nexport function getPrompt(req: GetPromptRequest): GetPromptResult {\n const name = req.params.name;\n const builder = QUESTIONS[name];\n if (!builder) throw new Error(`unknown prompt: ${name}`);\n const question = builder((req.params.arguments ?? {}) as Record<string, string>);\n return {\n description: prompts.find((p) => p.name === name)?.description ?? '',\n messages: [\n {\n role: 'user',\n content: { type: 'text', text: question },\n },\n ],\n };\n}\n","interface GrowthApiOptions {\n apiKey?: string | undefined;\n sessionToken?: string | undefined;\n host: string;\n projectId?: string | undefined;\n}\n\nexport class GrowthApi {\n constructor(private readonly options: GrowthApiOptions) {}\n\n // ─── read endpoints ───────────────────────────────────────────────────\n\n async getOverview(range: string): Promise<unknown> {\n return this.get(`/v1/overview?range=${encodeURIComponent(range)}`);\n }\n\n async getMetricSeries(metric: string, range: string): Promise<unknown> {\n return this.get(\n `/v1/metrics/series?metric=${encodeURIComponent(metric)}&range=${encodeURIComponent(range)}`,\n );\n }\n\n async getFunnel(name: string, range: string): Promise<unknown> {\n return this.get(`/v1/funnels/${encodeURIComponent(name)}?range=${encodeURIComponent(range)}`);\n }\n\n async listFunnels(): Promise<unknown> {\n return this.get('/v1/funnels');\n }\n\n async getRetention(range: string): Promise<unknown> {\n return this.get(`/v1/retention?range=${encodeURIComponent(range)}`);\n }\n\n async listAnomalies(severity?: string): Promise<unknown> {\n const qs = severity ? `?severity=${encodeURIComponent(severity)}` : '';\n return this.get(`/v1/anomalies${qs}`);\n }\n\n async listCommits(since: string): Promise<unknown> {\n return this.get(`/v1/commits?since=${encodeURIComponent(since)}`);\n }\n\n async getPeerBenchmarks(): Promise<unknown> {\n return this.get('/v1/benchmarks/peers');\n }\n\n async listHypotheses(): Promise<unknown> {\n return this.get('/v1/hypotheses');\n }\n\n // ─── agent endpoints ──────────────────────────────────────────────────\n\n async diagnose(metric: string, since: string): Promise<unknown> {\n return this.post('/v1/agent/diagnose', { metric, since });\n }\n\n async whyEvent(event: string): Promise<unknown> {\n return this.post('/v1/agent/why_event', { event });\n }\n\n /**\n * Talk to the AI growth engineer. Creates an ephemeral thread, posts the\n * question, returns the assistant reply with citations. Requires a\n * session token (cookie auth) — api-keys don't reach this endpoint.\n */\n async growthEngineerAsk(content: string): Promise<unknown> {\n const created = (await this.post('/v1/growth-engineer/threads', {})) as { id: string };\n return this.post(`/v1/growth-engineer/threads/${created.id}/messages`, { content });\n }\n\n // ─── transport ────────────────────────────────────────────────────────\n\n private async get(path: string): Promise<unknown> {\n return this.request('GET', path);\n }\n\n private async post(path: string, body: unknown): Promise<unknown> {\n return this.request('POST', path, body);\n }\n\n private async request(method: string, path: string, body?: unknown): Promise<unknown> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n };\n if (this.options.apiKey) headers['x-growth-api-key'] = this.options.apiKey;\n if (this.options.sessionToken) headers['cookie'] = `growth_session=${this.options.sessionToken}`;\n if (this.options.projectId) headers['x-growth-project'] = this.options.projectId;\n\n const res = await fetch(`${this.options.host}${path}`, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`growth-api ${res.status} ${path}: ${text}`);\n }\n return res.json();\n }\n}\n"],"mappings":";;;AAAA,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACJP,SAAS,SAAS;AAGlB,IAAM,YAAY,EAAE,KAAK,CAAC,MAAM,OAAO,MAAM,OAAO,KAAK,CAAC;AAE1D,IAAM,SAAS;AAAA,EACb,UAAU,EAAE,OAAO,EAAE,OAAO,UAAU,QAAQ,KAAK,EAAE,CAAC;AAAA,EACtD,eAAe,EAAE,OAAO;AAAA,IACtB,QAAQ,EAAE,OAAO;AAAA,IACjB,OAAO,UAAU,QAAQ,KAAK;AAAA,EAChC,CAAC;AAAA,EACD,YAAY,EAAE,OAAO;AAAA,IACnB,MAAM,EAAE,OAAO;AAAA,IACf,OAAO,UAAU,QAAQ,KAAK;AAAA,EAChC,CAAC;AAAA,EACD,cAAc,EAAE,OAAO,CAAC,CAAC;AAAA,EACzB,WAAW,EAAE,OAAO,EAAE,OAAO,UAAU,QAAQ,KAAK,EAAE,CAAC;AAAA,EACvD,UAAU,EAAE,OAAO;AAAA,IACjB,QAAQ,EAAE,OAAO;AAAA,IACjB,OAAO,EAAE,OAAO,EAAE,QAAQ,KAAK;AAAA,EACjC,CAAC;AAAA,EACD,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,EACzC,gBAAgB,EAAE,OAAO;AAAA,IACvB,UAAU,EAAE,KAAK,CAAC,QAAQ,QAAQ,UAAU,CAAC,EAAE,SAAS;AAAA,EAC1D,CAAC;AAAA,EACD,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,KAAK,EAAE,CAAC;AAAA,EAC3D,gBAAgB,EAAE,OAAO,CAAC,CAAC;AAAA,EAC3B,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAAA,EAC5B,qBAAqB,EAAE,OAAO;AAAA,IAC5B,UAAU,EACP,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAI,EACR,SAAS,8CAA8C;AAAA,EAC5D,CAAC;AACH;AAEO,IAAM,QAAgB;AAAA,EAC3B;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa,UAAU,OAAO,QAAQ;AAAA,EACxC;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,UAAU,OAAO,aAAa;AAAA,EAC7C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,sBAAsB,MAAM;AAAA,EAC7E;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,UAAU,OAAO,UAAU;AAAA,EAC1C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,UAAU,OAAO,SAAS;AAAA,EACzC;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa,UAAU,OAAO,QAAQ;AAAA,EACxC;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa,UAAU,OAAO,SAAS;AAAA,EACzC;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,UAAU,OAAO,cAAc;AAAA,EAC9C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,UAAU,OAAO,YAAY;AAAA,EAC5C;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,sBAAsB,MAAM;AAAA,EAC7E;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,EAAE,MAAM,UAAU,YAAY,CAAC,GAAG,sBAAsB,MAAM;AAAA,EAC7E;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa,UAAU,OAAO,mBAAmB;AAAA,EACnD;AACF;AAEA,eAAsB,SAASA,MAAgB,KAA+C;AAC5F,QAAM,OAAO,IAAI,OAAO;AACxB,QAAM,OAAO,IAAI,OAAO,aAAa,CAAC;AAEtC,UAAQ,MAAM;AAAA,IACZ,KAAK,YAAY;AACf,YAAM,QAAQ,OAAO,SAAS,MAAM,IAAI;AACxC,aAAO,KAAK,MAAMA,KAAI,YAAY,MAAM,KAAK,CAAC;AAAA,IAChD;AAAA,IACA,KAAK,iBAAiB;AACpB,YAAM,QAAQ,OAAO,cAAc,MAAM,IAAI;AAC7C,aAAO,KAAK,MAAMA,KAAI,gBAAgB,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,IAClE;AAAA,IACA,KAAK,gBAAgB;AACnB,aAAO,KAAK,MAAMA,KAAI,YAAY,CAAC;AAAA,IACrC;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,QAAQ,OAAO,WAAW,MAAM,IAAI;AAC1C,aAAO,KAAK,MAAMA,KAAI,UAAU,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,IAC1D;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,QAAQ,OAAO,UAAU,MAAM,IAAI;AACzC,aAAO,KAAK,MAAMA,KAAI,aAAa,MAAM,KAAK,CAAC;AAAA,IACjD;AAAA,IACA,KAAK,YAAY;AACf,YAAM,QAAQ,OAAO,SAAS,MAAM,IAAI;AACxC,aAAO,KAAK,MAAMA,KAAI,SAAS,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,IAC3D;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,QAAQ,OAAO,UAAU,MAAM,IAAI;AACzC,aAAO,KAAK,MAAMA,KAAI,SAAS,MAAM,KAAK,CAAC;AAAA,IAC7C;AAAA,IACA,KAAK,kBAAkB;AACrB,YAAM,QAAQ,OAAO,eAAe,MAAM,IAAI;AAC9C,aAAO,KAAK,MAAMA,KAAI,cAAc,MAAM,QAAQ,CAAC;AAAA,IACrD;AAAA,IACA,KAAK,gBAAgB;AACnB,YAAM,QAAQ,OAAO,aAAa,MAAM,IAAI;AAC5C,aAAO,KAAK,MAAMA,KAAI,YAAY,MAAM,KAAK,CAAC;AAAA,IAChD;AAAA,IACA,KAAK,kBAAkB;AACrB,aAAO,KAAK,MAAMA,KAAI,kBAAkB,CAAC;AAAA,IAC3C;AAAA,IACA,KAAK,mBAAmB;AACtB,aAAO,KAAK,MAAMA,KAAI,eAAe,CAAC;AAAA,IACxC;AAAA,IACA,KAAK,uBAAuB;AAC1B,YAAM,QAAQ,OAAO,oBAAoB,MAAM,IAAI;AACnD,aAAO,KAAK,MAAMA,KAAI,kBAAkB,MAAM,QAAQ,CAAC;AAAA,IACzD;AAAA,IACA;AACE,YAAM,IAAI,MAAM,iBAAiB,IAAI,EAAE;AAAA,EAC3C;AACF;AAEA,SAAS,KAAK,SAAkC;AAC9C,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC;AAAA,EACpE;AACF;AAEA,SAAS,UAAU,QAA2C;AAC5D,QAAM,QAAS,OAAqD;AACpE,QAAM,aAAqC,CAAC;AAC5C,QAAM,WAAqB,CAAC;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,eAAW,GAAG,IAAI,SAAS,KAAK;AAChC,QAAI,CAAC,MAAM,WAAW,EAAG,UAAS,KAAK,GAAG;AAAA,EAC5C;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,sBAAsB;AAAA,EACxB;AACF;AAEA,SAAS,SAAS,QAA+C;AAC/D,MAAI,kBAAkB,EAAE,WAAW;AACjC,UAAM,OAAO,OAAO;AACpB,WAAO,OAAO,EAAE,MAAM,UAAU,aAAa,KAAK,IAAI,EAAE,MAAM,SAAS;AAAA,EACzE;AACA,MAAI,kBAAkB,EAAE,UAAW,QAAO,EAAE,MAAM,SAAS;AAC3D,MAAI,kBAAkB,EAAE,WAAY,QAAO,EAAE,MAAM,UAAU;AAC7D,MAAI,kBAAkB,EAAE,QAAS,QAAO,EAAE,MAAM,UAAU,MAAM,OAAO,QAAQ;AAC/E,MAAI,kBAAkB,EAAE,YAAa,QAAO,SAAS,OAAO,OAAO,CAAC;AACpE,MAAI,kBAAkB,EAAE,WAAY,QAAO,SAAS,OAAO,cAAc,CAAC;AAC1E,SAAO,EAAE,MAAM,SAAS;AAC1B;;;AC/LO,IAAM,YAAwB;AAAA,EACnC;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,EACZ;AACF;AAEA,eAAsB,aACpBC,MACA,KAC6B;AAC7B,QAAM,EAAE,IAAI,IAAI,IAAI;AAEpB,MAAI,QAAQ,oBAAqB,QAAO,aAAa,KAAK,MAAMA,KAAI,YAAY,KAAK,CAAC;AACtF,MAAI,QAAQ,4BAA6B,QAAO,aAAa,KAAK,MAAMA,KAAI,cAAc,CAAC;AAC3F,MAAI,QAAQ,0BAA2B,QAAO,aAAa,KAAK,MAAMA,KAAI,YAAY,KAAK,CAAC;AAC5F,MAAI,QAAQ,4BAA6B,QAAO,aAAa,KAAK,MAAMA,KAAI,kBAAkB,CAAC;AAC/F,MAAI,QAAQ,sBAAuB,QAAO,aAAa,KAAK,MAAMA,KAAI,eAAe,CAAC;AAEtF,QAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AAC5C;AAEA,SAAS,aAAa,KAAa,MAAmC;AACpE,SAAO;AAAA,IACL,UAAU;AAAA,MACR;AAAA,QACE;AAAA,QACA,UAAU;AAAA,QACV,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACF;;;ACrDO,IAAM,UAAoB;AAAA,EAC/B;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,WAAW;AAAA,MACT;AAAA,QACE,MAAM;AAAA,QACN,aAAa;AAAA,QACb,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AACF;AAEA,IAAM,YAAsE;AAAA,EAC1E,QAAQ,MACN;AAAA,EACF,KAAK,MACH;AAAA,EACF,OAAO,MACL;AAAA,EACF,KAAK,MACH;AAAA,EACF,UAAU,MACR;AAAA,EACF,UAAU,CAAC,SACT,WAAW,KAAK,UAAU,aAAa;AAAA,EACzC,MAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyFd;AAEO,SAAS,UAAU,KAAwC;AAChE,QAAM,OAAO,IAAI,OAAO;AACxB,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,mBAAmB,IAAI,EAAE;AACvD,QAAM,WAAW,QAAS,IAAI,OAAO,aAAa,CAAC,CAA4B;AAC/E,SAAO;AAAA,IACL,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,eAAe;AAAA,IAClE,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS,EAAE,MAAM,QAAQ,MAAM,SAAS;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AACF;;;ACxKO,IAAM,YAAN,MAAgB;AAAA,EACrB,YAA6B,SAA2B;AAA3B;AAAA,EAA4B;AAAA,EAA5B;AAAA;AAAA,EAI7B,MAAM,YAAY,OAAiC;AACjD,WAAO,KAAK,IAAI,sBAAsB,mBAAmB,KAAK,CAAC,EAAE;AAAA,EACnE;AAAA,EAEA,MAAM,gBAAgB,QAAgB,OAAiC;AACrE,WAAO,KAAK;AAAA,MACV,6BAA6B,mBAAmB,MAAM,CAAC,UAAU,mBAAmB,KAAK,CAAC;AAAA,IAC5F;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,MAAc,OAAiC;AAC7D,WAAO,KAAK,IAAI,eAAe,mBAAmB,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC,EAAE;AAAA,EAC9F;AAAA,EAEA,MAAM,cAAgC;AACpC,WAAO,KAAK,IAAI,aAAa;AAAA,EAC/B;AAAA,EAEA,MAAM,aAAa,OAAiC;AAClD,WAAO,KAAK,IAAI,uBAAuB,mBAAmB,KAAK,CAAC,EAAE;AAAA,EACpE;AAAA,EAEA,MAAM,cAAc,UAAqC;AACvD,UAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,WAAO,KAAK,IAAI,gBAAgB,EAAE,EAAE;AAAA,EACtC;AAAA,EAEA,MAAM,YAAY,OAAiC;AACjD,WAAO,KAAK,IAAI,qBAAqB,mBAAmB,KAAK,CAAC,EAAE;AAAA,EAClE;AAAA,EAEA,MAAM,oBAAsC;AAC1C,WAAO,KAAK,IAAI,sBAAsB;AAAA,EACxC;AAAA,EAEA,MAAM,iBAAmC;AACvC,WAAO,KAAK,IAAI,gBAAgB;AAAA,EAClC;AAAA;AAAA,EAIA,MAAM,SAAS,QAAgB,OAAiC;AAC9D,WAAO,KAAK,KAAK,sBAAsB,EAAE,QAAQ,MAAM,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,SAAS,OAAiC;AAC9C,WAAO,KAAK,KAAK,uBAAuB,EAAE,MAAM,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,kBAAkB,SAAmC;AACzD,UAAM,UAAW,MAAM,KAAK,KAAK,+BAA+B,CAAC,CAAC;AAClE,WAAO,KAAK,KAAK,+BAA+B,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC;AAAA,EACpF;AAAA;AAAA,EAIA,MAAc,IAAI,MAAgC;AAChD,WAAO,KAAK,QAAQ,OAAO,IAAI;AAAA,EACjC;AAAA,EAEA,MAAc,KAAK,MAAc,MAAiC;AAChE,WAAO,KAAK,QAAQ,QAAQ,MAAM,IAAI;AAAA,EACxC;AAAA,EAEA,MAAc,QAAQ,QAAgB,MAAc,MAAkC;AACpF,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,QAAQ,OAAQ,SAAQ,kBAAkB,IAAI,KAAK,QAAQ;AACpE,QAAI,KAAK,QAAQ,aAAc,SAAQ,QAAQ,IAAI,kBAAkB,KAAK,QAAQ,YAAY;AAC9F,QAAI,KAAK,QAAQ,UAAW,SAAQ,kBAAkB,IAAI,KAAK,QAAQ;AAEvE,UAAM,MAAM,MAAM,MAAM,GAAG,KAAK,QAAQ,IAAI,GAAG,IAAI,IAAI;AAAA,MACrD;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAY,KAAK,UAAU,IAAI,IAAI;AAAA,IACpD,CAAC;AAED,QAAI,CAAC,IAAI,IAAI;AACX,YAAMC,QAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,YAAM,IAAI,MAAM,cAAc,IAAI,MAAM,IAAI,IAAI,KAAKA,KAAI,EAAE;AAAA,IAC7D;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;;;AJtFA,IAAM,SAAS,QAAQ,IAAI;AAC3B,IAAM,eAAe,QAAQ,IAAI;AACjC,IAAM,OAAO,QAAQ,IAAI,eAAe;AACxC,IAAM,YAAY,QAAQ,IAAI;AAE9B,IAAI,CAAC,UAAU,CAAC,cAAc;AAC5B,UAAQ,OAAO;AAAA,IACb;AAAA,EAEF;AACA,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,MAAM,IAAI,UAAU,EAAE,QAAQ,cAAc,MAAM,UAAU,CAAC;AAEnE,IAAM,SAAS,IAAI;AAAA,EACjB,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,EACxC,EAAE,cAAc,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE;AAC5D;AAEA,OAAO,kBAAkB,wBAAwB,aAAa,EAAE,MAAM,EAAE;AACxE,OAAO,kBAAkB,uBAAuB,OAAO,QAAQ,SAAS,KAAK,GAAG,CAAC;AAEjF,OAAO,kBAAkB,4BAA4B,aAAa,EAAE,UAAU,EAAE;AAChF,OAAO,kBAAkB,2BAA2B,OAAO,QAAQ,aAAa,KAAK,GAAG,CAAC;AAEzF,OAAO,kBAAkB,0BAA0B,aAAa,EAAE,QAAQ,EAAE;AAC5E,OAAO,kBAAkB,wBAAwB,OAAO,QAAQ,UAAU,GAAG,CAAC;AAE9E,IAAM,YAAY,IAAI,qBAAqB;AAC3C,MAAM,OAAO,QAAQ,SAAS;","names":["api","api","text"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@growth-loop/mcp-server",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server for growth-loop.dev — an AI growth engineer for vibe-coded SaaS. Exposes funnels, retention, peer benchmarks, and the growth engineer to Claude Desktop, Claude Code, and any MCP-compatible client.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://growth-loop.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://gitlab.com/AKnyaZP/metrics.git",
|
|
10
|
+
"directory": "packages/mcp-server"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://gitlab.com/AKnyaZP/metrics/-/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-desktop",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"product-analytics",
|
|
22
|
+
"saas",
|
|
23
|
+
"indie-hackers",
|
|
24
|
+
"growth-loop"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"growth-mcp": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsup": "^8.3.0",
|
|
46
|
+
"typescript": "^5.6.3"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"start": "node dist/index.js",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "echo 'no lint'",
|
|
54
|
+
"clean": "rm -rf dist .turbo"
|
|
55
|
+
}
|
|
56
|
+
}
|