@growpanel/mcp-server 2.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/README.md +137 -0
- package/bin/cli.js +2 -0
- package/dist/api.d.ts +9 -0
- package/dist/api.js +54 -0
- package/dist/money_scale.d.ts +1 -0
- package/dist/money_scale.js +76 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +63 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.js +470 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# GrowPanel MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server for [GrowPanel](https://growpanel.io) subscription analytics. Connects AI assistants (Claude, Cursor, Windsurf, etc.) to your GrowPanel data via the Model Context Protocol.
|
|
4
|
+
|
|
5
|
+
**14 tools** covering reports, customers, plans, settings, webhooks, billing, team, and more. Plus a generic API passthrough that automatically supports new API features.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @growpanel/mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
### Claude Desktop
|
|
16
|
+
|
|
17
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"growpanel": {
|
|
23
|
+
"command": "growpanel-mcp",
|
|
24
|
+
"env": {
|
|
25
|
+
"GROWPANEL_API_KEY": "your-api-key-here"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Cursor / Other MCP Clients
|
|
33
|
+
|
|
34
|
+
Most MCP clients support a similar configuration. Set the command to `growpanel-mcp` and provide the `GROWPANEL_API_KEY` environment variable.
|
|
35
|
+
|
|
36
|
+
### Environment Variables
|
|
37
|
+
|
|
38
|
+
| Variable | Description | Default |
|
|
39
|
+
|----------|-------------|---------|
|
|
40
|
+
| `GROWPANEL_API_KEY` | Your GrowPanel API key (required) | — |
|
|
41
|
+
| `GROWPANEL_API_URL` | API base URL | `https://api.growpanel.io` |
|
|
42
|
+
|
|
43
|
+
For backward compatibility, `GROWPANEL_API_TOKEN` is also accepted.
|
|
44
|
+
|
|
45
|
+
Get your API key at [app.growpanel.io](https://app.growpanel.io) → Settings → API Keys.
|
|
46
|
+
|
|
47
|
+
## Available Tools
|
|
48
|
+
|
|
49
|
+
### Analytics
|
|
50
|
+
|
|
51
|
+
| Tool | Description |
|
|
52
|
+
|------|-------------|
|
|
53
|
+
| `get_report` | Fetch any analytics report by name (mrr, summary, cohort, leads, cashflow, etc.) |
|
|
54
|
+
|
|
55
|
+
The `get_report` tool accepts any report name — new reports added to the API work automatically.
|
|
56
|
+
|
|
57
|
+
**Known reports:** mrr, mrr-table, summary, cmrr-summary, movement-table, map, cohort, leads, leads-table, leads-days, leads-summary, transactions, transactions-table, transactions-detail, transactions-summary, invoices-detail, churn-scheduled, churn-scheduled-movements, churn-scheduled-summary, customer-concentration, cashflow-failed-payments, cashflow-failed-payments-summary, cashflow-refunds, cashflow-refunds-table, cashflow-failure-rate, cashflow-outstanding-unpaid, custom-variables
|
|
58
|
+
|
|
59
|
+
### Customers & Plans
|
|
60
|
+
|
|
61
|
+
| Tool | Description |
|
|
62
|
+
|------|-------------|
|
|
63
|
+
| `list_customers` | List customers with MRR and subscription details |
|
|
64
|
+
| `get_customer` | Get detailed info for a specific customer |
|
|
65
|
+
| `resync_customer` | Trigger a data resync from the payment provider |
|
|
66
|
+
| `list_plans` | List all subscription plans and plan groups |
|
|
67
|
+
|
|
68
|
+
### Data Management
|
|
69
|
+
|
|
70
|
+
| Tool | Description |
|
|
71
|
+
|------|-------------|
|
|
72
|
+
| `manage_data` | CRUD operations on customers, plans, plan-groups, data-sources, invoices |
|
|
73
|
+
| `export_csv` | Export customers or MRR movements as CSV |
|
|
74
|
+
|
|
75
|
+
### Settings & Integrations
|
|
76
|
+
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `manage_settings` | Get/update account and integration settings |
|
|
80
|
+
| `manage_webhooks` | Manage webhook subscriptions (list, create, delete, verify, sample) |
|
|
81
|
+
|
|
82
|
+
### Account Management
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `manage_account` | Get, update, or delete the account |
|
|
87
|
+
| `manage_billing` | Billing details, invoices, subscriptions, payment methods |
|
|
88
|
+
| `manage_team` | Team members: list, invite, edit roles, remove |
|
|
89
|
+
| `manage_api_keys` | API key management: list, create, update, delete |
|
|
90
|
+
|
|
91
|
+
### Generic Passthrough
|
|
92
|
+
|
|
93
|
+
| Tool | Description |
|
|
94
|
+
|------|-------------|
|
|
95
|
+
| `api_request` | Call any API endpoint directly (method + path + params/body) |
|
|
96
|
+
|
|
97
|
+
The `api_request` tool supports any API endpoint. New features added to the GrowPanel API are immediately available through this tool without needing an MCP server update.
|
|
98
|
+
|
|
99
|
+
## Example Prompts
|
|
100
|
+
|
|
101
|
+
Once connected, you can ask your AI assistant:
|
|
102
|
+
|
|
103
|
+
- "What's my current MRR?"
|
|
104
|
+
- "Show me MRR trends for the last 6 months"
|
|
105
|
+
- "List my top customers by revenue"
|
|
106
|
+
- "What's my churn rate?"
|
|
107
|
+
- "Show me the cohort retention analysis"
|
|
108
|
+
- "How many leads converted this quarter?"
|
|
109
|
+
- "List all my webhook subscriptions"
|
|
110
|
+
- "What are my current settings?"
|
|
111
|
+
|
|
112
|
+
## Self-Updating Architecture
|
|
113
|
+
|
|
114
|
+
The MCP server uses the same dynamic architecture as the [GrowPanel CLI](https://www.npmjs.com/package/@growpanel/cli):
|
|
115
|
+
|
|
116
|
+
- **`get_report`** accepts any report name and passes it through to `/reports/<name>`. New reports work automatically.
|
|
117
|
+
- **`api_request`** can call any API endpoint. New API features work on day one.
|
|
118
|
+
- Named tools (`manage_data`, `manage_settings`, etc.) provide better descriptions for AI understanding, but the passthrough ensures nothing is missed.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm install
|
|
124
|
+
npm run dev # Run from source via tsx
|
|
125
|
+
npm run build # Compile TypeScript
|
|
126
|
+
npm run typecheck # Type checking only
|
|
127
|
+
npm start # Run built version
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Requirements
|
|
131
|
+
|
|
132
|
+
- Node.js 20+
|
|
133
|
+
- GrowPanel account with API key
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|
package/bin/cli.js
ADDED
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function callApi(options: {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
params?: Record<string, string>;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
}): Promise<unknown>;
|
|
7
|
+
export declare function unwrap(data: unknown): unknown;
|
|
8
|
+
declare function filterParams(obj: Record<string, unknown>): Record<string, string>;
|
|
9
|
+
export { filterParams };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const DEFAULT_API_URL = 'https://api.growpanel.io';
|
|
2
|
+
export async function callApi(options) {
|
|
3
|
+
const baseUrl = (process.env.GROWPANEL_API_URL || DEFAULT_API_URL).replace(/\/$/, '');
|
|
4
|
+
const apiKey = process.env.GROWPANEL_API_KEY || process.env.GROWPANEL_API_TOKEN;
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
throw new Error('API key not configured. Set GROWPANEL_API_KEY environment variable. ' +
|
|
7
|
+
'Get your key at https://app.growpanel.io → Settings → API Keys.');
|
|
8
|
+
}
|
|
9
|
+
const url = new URL(options.path, baseUrl);
|
|
10
|
+
if (options.params) {
|
|
11
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
12
|
+
if (value !== undefined && value !== '') {
|
|
13
|
+
url.searchParams.set(key, value);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const headers = {
|
|
18
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
};
|
|
21
|
+
const res = await fetch(url.toString(), {
|
|
22
|
+
method: options.method,
|
|
23
|
+
headers,
|
|
24
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) {
|
|
27
|
+
const errorText = await res.text();
|
|
28
|
+
throw new Error(`API error ${res.status}: ${errorText}`);
|
|
29
|
+
}
|
|
30
|
+
if (res.status === 204) {
|
|
31
|
+
return { success: true };
|
|
32
|
+
}
|
|
33
|
+
const contentType = res.headers.get('content-type') || '';
|
|
34
|
+
if (contentType.includes('text/csv') || contentType.includes('text/plain')) {
|
|
35
|
+
return await res.text();
|
|
36
|
+
}
|
|
37
|
+
return await res.json();
|
|
38
|
+
}
|
|
39
|
+
export function unwrap(data) {
|
|
40
|
+
if (data && typeof data === 'object' && 'result' in data) {
|
|
41
|
+
return data.result;
|
|
42
|
+
}
|
|
43
|
+
return data;
|
|
44
|
+
}
|
|
45
|
+
function filterParams(obj) {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
48
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
49
|
+
result[key] = String(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
export { filterParams };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const scaleMoneyFields: (value: unknown) => unknown;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Pre-scale monetary values in tool results before the AI client sees them.
|
|
2
|
+
//
|
|
3
|
+
// GrowPanel stores most amounts in the smallest currency unit (cents/øre/…),
|
|
4
|
+
// so a display value is the raw ÷ 100. A handful of "zero-decimal" currencies
|
|
5
|
+
// (JPY, KRW, ISK, VND, HUF, …) have no subunit — the raw value IS the display
|
|
6
|
+
// value and must NOT be divided. We detect the containing currency from the
|
|
7
|
+
// nearest `currency` key in scope and act accordingly.
|
|
8
|
+
//
|
|
9
|
+
// Without this, the AI client routinely treats raw cents as dollars and quotes
|
|
10
|
+
// numbers ~100× too high. Prompting alone can't be relied on; doing it in the
|
|
11
|
+
// tool layer is the robust fix.
|
|
12
|
+
//
|
|
13
|
+
// Mirrors the same logic used by the hosted MCP server (growpanel-mcp/src/
|
|
14
|
+
// helpers/money_scale.js) and the in-app AI chat
|
|
15
|
+
// (growpanel-api/src/controllers/chat/v2/tools/_money_scale.js). Keep all
|
|
16
|
+
// three in sync if you add new amount fields.
|
|
17
|
+
const ZERO_DECIMAL_CURRENCIES = new Set([
|
|
18
|
+
'bif', 'clp', 'djf', 'gnf', 'isk', 'jpy', 'kmf', 'krw', 'mga', 'pyg',
|
|
19
|
+
'rwf', 'ugx', 'vnd', 'vuv', 'xaf', 'xof', 'xpf', 'huf',
|
|
20
|
+
]);
|
|
21
|
+
const MONEY_EXPLICIT = new Set([
|
|
22
|
+
'new', 'expansion', 'contraction', 'churn', 'reactivation',
|
|
23
|
+
'mrr_diff', 'net_mrr_diff', 'fx_adjustment', 'total_mrr', 'total_arr',
|
|
24
|
+
'arpa', 'asp', 'ltv',
|
|
25
|
+
'net_amount', 'month_sub', 'year_sub', 'one_time', 'metered',
|
|
26
|
+
'discount', 'refund', 'tax', 'fee', 'fx_loss',
|
|
27
|
+
'failed_amount', 'recovered', 'still_unpaid', 'churned',
|
|
28
|
+
'scheduled_churn_mrr',
|
|
29
|
+
'cmrr_current', 'cmrr_30', 'cmrr_60', 'cmrr_180', 'cmrr_365',
|
|
30
|
+
'mrr', 'amount', 'original_amount',
|
|
31
|
+
'total_paid_customer_currency', 'total_paid_base_currency',
|
|
32
|
+
'source_mrr',
|
|
33
|
+
'mrr_change', 'mrr_change_base_currency', 'mrr_change_customer_currency',
|
|
34
|
+
]);
|
|
35
|
+
const NOT_MONEY_RE = /(_count|_counts|_customers?|_rate|_rates|_pct|_percent|_percentage|_days|_num|_ratio|_change_pct|_diff_pct|_id)$/i;
|
|
36
|
+
const MONEY_HINT_RE = /^(mrr|arr|cmrr|ltv|arpa|asp|cac|gmv|revenue|amount|paid|charged|refunded|collected|owed|earned|invoiced|billed|churned|recovered|discount|fee|tax|gross|net)_|_(mrr|arr|ltv|amount|paid|charged|refunded|collected|revenue|owed|earned|invoiced|billed|churned|recovered|fee|tax|discount|gross|net)(_|$)/i;
|
|
37
|
+
const looksLikeMoneyKey = (key) => {
|
|
38
|
+
if (!key)
|
|
39
|
+
return false;
|
|
40
|
+
if (MONEY_EXPLICIT.has(key))
|
|
41
|
+
return true;
|
|
42
|
+
if (NOT_MONEY_RE.test(key))
|
|
43
|
+
return false;
|
|
44
|
+
return MONEY_HINT_RE.test(key);
|
|
45
|
+
};
|
|
46
|
+
const isZeroDecimal = (code) => typeof code === 'string' && ZERO_DECIMAL_CURRENCIES.has(code.trim().toLowerCase());
|
|
47
|
+
const scaleNumber = (n) => {
|
|
48
|
+
if (typeof n !== 'number' || !isFinite(n))
|
|
49
|
+
return n;
|
|
50
|
+
return Math.round(n) / 100;
|
|
51
|
+
};
|
|
52
|
+
const walk = (value, currencyInScope) => {
|
|
53
|
+
if (value == null)
|
|
54
|
+
return value;
|
|
55
|
+
if (Array.isArray(value))
|
|
56
|
+
return value.map((v) => walk(v, currencyInScope));
|
|
57
|
+
if (typeof value !== 'object')
|
|
58
|
+
return value;
|
|
59
|
+
const obj = value;
|
|
60
|
+
const localCurrency = typeof obj.currency === 'string' ? obj.currency : currencyInScope;
|
|
61
|
+
const skip = isZeroDecimal(localCurrency);
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
64
|
+
if (looksLikeMoneyKey(k) && typeof v === 'number' && !skip) {
|
|
65
|
+
out[k] = scaleNumber(v);
|
|
66
|
+
}
|
|
67
|
+
else if (v && typeof v === 'object') {
|
|
68
|
+
out[k] = walk(v, localCurrency);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
out[k] = v;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
};
|
|
76
|
+
export const scaleMoneyFields = (value) => walk(value, null);
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
dotenv.config();
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { tools } from './tools.js';
|
|
7
|
+
import { scaleMoneyFields } from './money_scale.js';
|
|
8
|
+
const server = new Server({
|
|
9
|
+
name: 'GrowPanel MCP Server',
|
|
10
|
+
version: '2.1.0',
|
|
11
|
+
}, {
|
|
12
|
+
capabilities: {
|
|
13
|
+
tools: {},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
// Register tools/list handler
|
|
17
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
18
|
+
return {
|
|
19
|
+
tools: tools.map((tool) => ({
|
|
20
|
+
name: tool.name,
|
|
21
|
+
description: tool.description,
|
|
22
|
+
inputSchema: tool.inputSchema,
|
|
23
|
+
})),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
// Register tools/call handler
|
|
27
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
28
|
+
const { name, arguments: args } = request.params;
|
|
29
|
+
const tool = tools.find((t) => t.name === name);
|
|
30
|
+
if (!tool) {
|
|
31
|
+
throw new Error(`Unknown tool: ${name}. Available tools: ${tools.map((t) => t.name).join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const raw = await tool.handler(args || {});
|
|
35
|
+
// Pre-scale monetary fields (cents → display units) so the AI client
|
|
36
|
+
// doesn't quote raw cents as if they were dollars. CSV/text returns
|
|
37
|
+
// pass through untouched.
|
|
38
|
+
const result = typeof raw === 'string' ? raw : scaleMoneyFields(raw);
|
|
39
|
+
const text = typeof result === 'string'
|
|
40
|
+
? result
|
|
41
|
+
: JSON.stringify(result, null, 2);
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: 'text', text }],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Start stdio transport
|
|
55
|
+
async function main() {
|
|
56
|
+
const transport = new StdioServerTransport();
|
|
57
|
+
await server.connect(transport);
|
|
58
|
+
console.error('GrowPanel MCP server running on stdio');
|
|
59
|
+
}
|
|
60
|
+
main().catch((error) => {
|
|
61
|
+
console.error('Failed to start MCP server:', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface ToolDef {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
handler: (params: Record<string, any>) => Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare const tools: ToolDef[];
|
|
12
|
+
export {};
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { callApi, unwrap, filterParams } from './api.js';
|
|
2
|
+
const KNOWN_REPORTS = [
|
|
3
|
+
'mrr', 'mrr-growth', 'mrr-growth-table', 'mrr-table', 'mrr-table-subtypes', 'summary', 'cmrr-summary',
|
|
4
|
+
'movement-table', 'map', 'cohort',
|
|
5
|
+
// retention is the source of truth for churn, retention, NRR, GRR, LTV, customer_lifetime.
|
|
6
|
+
// The mrr report no longer exposes those fields — query retention instead.
|
|
7
|
+
'retention',
|
|
8
|
+
'leads', 'leads-table', 'leads-days', 'leads-summary',
|
|
9
|
+
'transactions', 'transactions-table', 'transactions-detail', 'transactions-summary',
|
|
10
|
+
'invoices-detail',
|
|
11
|
+
'churn-scheduled', 'churn-scheduled-movements', 'churn-scheduled-summary',
|
|
12
|
+
'customer-concentration',
|
|
13
|
+
'cashflow-failed-payments', 'cashflow-failed-payments-summary',
|
|
14
|
+
'cashflow-failed-payments-detail', 'cashflow-failed-payments-table',
|
|
15
|
+
'cashflow-refunds', 'cashflow-refunds-table', 'cashflow-refunds-detail',
|
|
16
|
+
'cashflow-failure-rate', 'cashflow-failure-rate-summary',
|
|
17
|
+
'cashflow-outstanding-unpaid',
|
|
18
|
+
'custom-variables',
|
|
19
|
+
];
|
|
20
|
+
const REPORT_FILTER_PROPERTIES = {
|
|
21
|
+
date: { type: 'string', description: 'Date range in yyyyMMdd-yyyyMMdd format (e.g., 20240101-20241231)' },
|
|
22
|
+
interval: { type: 'string', enum: ['day', 'week', 'month', 'quarter', 'year'], description: 'Aggregation interval (default: month)' },
|
|
23
|
+
currency: { type: 'string', description: 'Filter by currency code (e.g., usd, eur)' },
|
|
24
|
+
region: { type: 'string', description: 'Filter by region' },
|
|
25
|
+
plan: { type: 'string', description: 'Filter by plan group ID' },
|
|
26
|
+
country: { type: 'string', description: 'Filter by ISO country code' },
|
|
27
|
+
data_source: { type: 'string', description: 'Filter by data source ID' },
|
|
28
|
+
breakdown: { type: 'string', description: 'Group results by a dimension. Supported on mrr, retention, cohort, leads, leads-table, transactions (cashflow), transactions-table, cashflow-refunds, churn-reasons, churn-scheduled. Common values: plan, currency, payment_method, country, region, market, age, data_source, billing_freq, pricing_model. Custom variables: custom_<key>.' },
|
|
29
|
+
category: { type: 'string', description: 'Filter to specific movement types (space-separated): new, expansion, reactivation, contraction, churn. Used by mrr-movements and mrr-growth reports.' },
|
|
30
|
+
};
|
|
31
|
+
export const tools = [
|
|
32
|
+
// ─── Analytics (core) ────────────────────────────────────────────
|
|
33
|
+
{
|
|
34
|
+
name: 'get_report',
|
|
35
|
+
description: `Fetch any GrowPanel analytics report by name. This is the primary tool for subscription analytics.\n\nKnown reports: ${KNOWN_REPORTS.join(', ')}\n\nAny report name is accepted — new API reports work automatically without MCP server updates.\n\nAll monetary values are in the account's base currency. The response includes a \`currency\` field indicating which currency is used (e.g., "usd", "eur", "dkk").`,
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
name: { type: 'string', description: 'Report name (e.g., mrr, summary, cohort, leads, cashflow-failed-payments)' },
|
|
40
|
+
...REPORT_FILTER_PROPERTIES,
|
|
41
|
+
},
|
|
42
|
+
required: ['name'],
|
|
43
|
+
},
|
|
44
|
+
handler: async (params) => {
|
|
45
|
+
const { name, ...filters } = params;
|
|
46
|
+
const data = await callApi({ method: 'GET', path: `/reports/${name}`, params: filterParams(filters) });
|
|
47
|
+
return data;
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// ─── Customers ───────────────────────────────────────────────────
|
|
51
|
+
{
|
|
52
|
+
name: 'list_customers',
|
|
53
|
+
description: 'List all customers with their subscription details and MRR. Supports pagination.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
date: { type: 'string', description: 'Date range in yyyyMMdd-yyyyMMdd format' },
|
|
58
|
+
limit: { type: 'string', description: 'Maximum number of results (default: 100)' },
|
|
59
|
+
offset: { type: 'string', description: 'Pagination offset' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
handler: async (params) => {
|
|
63
|
+
const data = await callApi({ method: 'GET', path: '/customers', params: filterParams(params) });
|
|
64
|
+
return unwrap(data);
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'get_customer',
|
|
69
|
+
description: 'Get detailed information for a specific customer by ID, including MRR, plans, and subscription history.',
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
id: { type: 'string', description: 'Customer ID' },
|
|
74
|
+
},
|
|
75
|
+
required: ['id'],
|
|
76
|
+
},
|
|
77
|
+
handler: async (params) => {
|
|
78
|
+
const data = await callApi({ method: 'GET', path: `/customers/${params.id}` });
|
|
79
|
+
return unwrap(data);
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'resync_customer',
|
|
84
|
+
description: 'Trigger a data resync for a specific customer from the payment provider. Admin/owner only.',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
id: { type: 'string', description: 'Customer ID to resync' },
|
|
89
|
+
},
|
|
90
|
+
required: ['id'],
|
|
91
|
+
},
|
|
92
|
+
handler: async (params) => {
|
|
93
|
+
const data = await callApi({ method: 'POST', path: `/customers/${params.id}/resync` });
|
|
94
|
+
return unwrap(data);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// ─── Plans ───────────────────────────────────────────────────────
|
|
98
|
+
{
|
|
99
|
+
name: 'list_plans',
|
|
100
|
+
description: 'List all subscription plans with their plan groups, pricing, and customer counts.',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {},
|
|
104
|
+
},
|
|
105
|
+
handler: async () => {
|
|
106
|
+
const data = await callApi({ method: 'GET', path: '/plans' });
|
|
107
|
+
return unwrap(data);
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
// ─── Data CRUD ───────────────────────────────────────────────────
|
|
111
|
+
{
|
|
112
|
+
name: 'manage_data',
|
|
113
|
+
description: `Generic CRUD operations on data resources.\n\nResources: customers, plans, plan-groups, data-sources, invoices\n\nStandard actions: list, get, create, update, delete\nData source actions: reset, connect, full-import, progress, abort\nPlan group actions: delete-multiple, ai-suggest, merge`,
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
resource: { type: 'string', enum: ['customers', 'plans', 'plan-groups', 'data-sources', 'invoices'], description: 'Resource type' },
|
|
118
|
+
action: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
enum: ['list', 'get', 'create', 'update', 'delete', 'reset', 'connect', 'full-import', 'progress', 'abort', 'delete-multiple', 'ai-suggest', 'merge'],
|
|
121
|
+
description: 'Operation to perform',
|
|
122
|
+
},
|
|
123
|
+
id: { type: 'string', description: 'Resource ID (required for get, update, delete, and data-source special actions)' },
|
|
124
|
+
body: { type: 'object', description: 'Request body (for create, update, and special actions)' },
|
|
125
|
+
data_source: { type: 'string', description: 'Filter by data source ID (for list action)' },
|
|
126
|
+
},
|
|
127
|
+
required: ['resource', 'action'],
|
|
128
|
+
},
|
|
129
|
+
handler: async (params) => {
|
|
130
|
+
const { resource, action, id, body, data_source } = params;
|
|
131
|
+
let data;
|
|
132
|
+
switch (action) {
|
|
133
|
+
case 'list':
|
|
134
|
+
data = await callApi({ method: 'GET', path: `/data/${resource}`, params: filterParams({ data_source }) });
|
|
135
|
+
break;
|
|
136
|
+
case 'get':
|
|
137
|
+
data = await callApi({ method: 'GET', path: `/data/${resource}/${id}` });
|
|
138
|
+
break;
|
|
139
|
+
case 'create':
|
|
140
|
+
data = await callApi({ method: 'POST', path: `/data/${resource}`, body });
|
|
141
|
+
break;
|
|
142
|
+
case 'update':
|
|
143
|
+
data = await callApi({ method: 'PUT', path: `/data/${resource}/${id}`, body });
|
|
144
|
+
break;
|
|
145
|
+
case 'delete':
|
|
146
|
+
data = await callApi({ method: 'DELETE', path: `/data/${resource}/${id}` });
|
|
147
|
+
break;
|
|
148
|
+
// Data source special actions
|
|
149
|
+
case 'reset':
|
|
150
|
+
case 'connect':
|
|
151
|
+
case 'full-import':
|
|
152
|
+
case 'abort':
|
|
153
|
+
data = await callApi({ method: 'POST', path: `/data/${resource}/${id}/${action}`, body });
|
|
154
|
+
break;
|
|
155
|
+
case 'progress':
|
|
156
|
+
data = await callApi({ method: 'GET', path: `/data/${resource}/${id}/progress` });
|
|
157
|
+
break;
|
|
158
|
+
// Plan group special actions
|
|
159
|
+
case 'delete-multiple':
|
|
160
|
+
case 'ai-suggest':
|
|
161
|
+
case 'merge':
|
|
162
|
+
data = await callApi({ method: 'POST', path: `/data/${resource}/${action}`, body });
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
throw new Error(`Unknown action: ${action}`);
|
|
166
|
+
}
|
|
167
|
+
return unwrap(data);
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
// ─── Exports ─────────────────────────────────────────────────────
|
|
171
|
+
{
|
|
172
|
+
name: 'export_csv',
|
|
173
|
+
description: 'Export data as CSV. Available exports: customers (all customers with details), mrr-movements (all MRR changes), mrr-growth (per-month-per-day cumulative MRR change in long format, ideal for cohort/pacing analysis).',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
type: { type: 'string', enum: ['customers', 'mrr-movements', 'mrr-growth'], description: 'Export type' },
|
|
178
|
+
date: { type: 'string', description: 'Date range in yyyyMMdd-yyyyMMdd format' },
|
|
179
|
+
category: { type: 'string', description: 'For mrr-growth: filter to specific movement types (space-separated): new, expansion, reactivation, contraction, churn' },
|
|
180
|
+
},
|
|
181
|
+
required: ['type'],
|
|
182
|
+
},
|
|
183
|
+
handler: async (params) => {
|
|
184
|
+
const { type, date, category } = params;
|
|
185
|
+
const filename = `${type}.csv`;
|
|
186
|
+
const data = await callApi({ method: 'GET', path: `/exports/${filename}`, params: filterParams({ date, category }) });
|
|
187
|
+
return data;
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
// ─── Settings ────────────────────────────────────────────────────
|
|
191
|
+
{
|
|
192
|
+
name: 'manage_settings',
|
|
193
|
+
description: `Get or update account settings and integration settings.\n\nFor general settings: action=get or action=update with body.\nFor integration settings: set the integration parameter.\n\nIntegrations: notifications, stripe, hubspot, webhook, slack, teams, looker-studio`,
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
action: { type: 'string', enum: ['get', 'update'], description: 'get to read settings, update to modify' },
|
|
198
|
+
integration: {
|
|
199
|
+
type: 'string',
|
|
200
|
+
enum: ['notifications', 'stripe', 'hubspot', 'webhook', 'slack', 'teams', 'looker-studio'],
|
|
201
|
+
description: 'Integration name (omit for general account settings)',
|
|
202
|
+
},
|
|
203
|
+
body: { type: 'object', description: 'Settings to update (required for update action)' },
|
|
204
|
+
},
|
|
205
|
+
required: ['action'],
|
|
206
|
+
},
|
|
207
|
+
handler: async (params) => {
|
|
208
|
+
const { action, integration, body } = params;
|
|
209
|
+
const path = integration ? `/settings/${integration}` : '/settings';
|
|
210
|
+
let data;
|
|
211
|
+
if (action === 'get') {
|
|
212
|
+
data = await callApi({ method: 'GET', path });
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Use PUT for notifications, POST for everything else
|
|
216
|
+
const method = integration === 'notifications' ? 'PUT' : 'POST';
|
|
217
|
+
data = await callApi({ method, path, body });
|
|
218
|
+
}
|
|
219
|
+
return unwrap(data);
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
// ─── Webhooks ────────────────────────────────────────────────────
|
|
223
|
+
{
|
|
224
|
+
name: 'manage_webhooks',
|
|
225
|
+
description: 'Manage webhook subscriptions for real-time event notifications.',
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: 'object',
|
|
228
|
+
properties: {
|
|
229
|
+
action: { type: 'string', enum: ['list', 'get', 'create', 'delete', 'verify', 'sample'], description: 'Webhook operation' },
|
|
230
|
+
id: { type: 'string', description: 'Webhook ID (for get/delete)' },
|
|
231
|
+
event: { type: 'string', description: 'Event type name (for sample, e.g., customer.created)' },
|
|
232
|
+
body: { type: 'object', description: 'Webhook configuration (for create: url, events array)' },
|
|
233
|
+
},
|
|
234
|
+
required: ['action'],
|
|
235
|
+
},
|
|
236
|
+
handler: async (params) => {
|
|
237
|
+
const { action, id, event, body } = params;
|
|
238
|
+
let data;
|
|
239
|
+
switch (action) {
|
|
240
|
+
case 'list':
|
|
241
|
+
data = await callApi({ method: 'GET', path: '/integrations/webhooks' });
|
|
242
|
+
break;
|
|
243
|
+
case 'get':
|
|
244
|
+
data = await callApi({ method: 'GET', path: `/integrations/webhooks/${id}` });
|
|
245
|
+
break;
|
|
246
|
+
case 'create':
|
|
247
|
+
data = await callApi({ method: 'POST', path: '/integrations/webhooks', body });
|
|
248
|
+
break;
|
|
249
|
+
case 'delete':
|
|
250
|
+
data = await callApi({ method: 'DELETE', path: `/integrations/webhooks/${id}` });
|
|
251
|
+
break;
|
|
252
|
+
case 'verify':
|
|
253
|
+
data = await callApi({ method: 'GET', path: '/integrations/verify' });
|
|
254
|
+
break;
|
|
255
|
+
case 'sample':
|
|
256
|
+
data = await callApi({ method: 'GET', path: `/integrations/sample/${event}` });
|
|
257
|
+
break;
|
|
258
|
+
default:
|
|
259
|
+
throw new Error(`Unknown webhook action: ${action}`);
|
|
260
|
+
}
|
|
261
|
+
return unwrap(data);
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
// ─── Account ─────────────────────────────────────────────────────
|
|
265
|
+
{
|
|
266
|
+
name: 'manage_account',
|
|
267
|
+
description: 'Get, update, or delete the GrowPanel account.',
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
action: { type: 'string', enum: ['get', 'update', 'delete'], description: 'Account operation' },
|
|
272
|
+
body: { type: 'object', description: 'Account data to update (for update action)' },
|
|
273
|
+
},
|
|
274
|
+
required: ['action'],
|
|
275
|
+
},
|
|
276
|
+
handler: async (params) => {
|
|
277
|
+
const { action, body } = params;
|
|
278
|
+
let data;
|
|
279
|
+
switch (action) {
|
|
280
|
+
case 'get':
|
|
281
|
+
data = await callApi({ method: 'GET', path: '/account' });
|
|
282
|
+
break;
|
|
283
|
+
case 'update':
|
|
284
|
+
data = await callApi({ method: 'POST', path: '/account', body });
|
|
285
|
+
break;
|
|
286
|
+
case 'delete':
|
|
287
|
+
data = await callApi({ method: 'DELETE', path: '/account' });
|
|
288
|
+
break;
|
|
289
|
+
default:
|
|
290
|
+
throw new Error(`Unknown account action: ${action}`);
|
|
291
|
+
}
|
|
292
|
+
return unwrap(data);
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
// ─── Billing ─────────────────────────────────────────────────────
|
|
296
|
+
{
|
|
297
|
+
name: 'manage_billing',
|
|
298
|
+
description: 'Manage billing, subscriptions, invoices, and payment methods.',
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
action: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
enum: [
|
|
305
|
+
'details', 'invoices', 'portal', 'subscribe', 'check_vat',
|
|
306
|
+
'address', 'setup', 'preview_change', 'change_plan',
|
|
307
|
+
'undo_cancellation', 'delete_card',
|
|
308
|
+
],
|
|
309
|
+
description: 'Billing operation',
|
|
310
|
+
},
|
|
311
|
+
body: { type: 'object', description: 'Request body (for subscribe, address, preview_change, change_plan)' },
|
|
312
|
+
card_id: { type: 'string', description: 'Card ID (for delete_card action)' },
|
|
313
|
+
vat_number: { type: 'string', description: 'VAT number (for check_vat action)' },
|
|
314
|
+
},
|
|
315
|
+
required: ['action'],
|
|
316
|
+
},
|
|
317
|
+
handler: async (params) => {
|
|
318
|
+
const { action, body, card_id, vat_number } = params;
|
|
319
|
+
let data;
|
|
320
|
+
switch (action) {
|
|
321
|
+
case 'details':
|
|
322
|
+
data = await callApi({ method: 'GET', path: '/account/billing' });
|
|
323
|
+
break;
|
|
324
|
+
case 'invoices':
|
|
325
|
+
data = await callApi({ method: 'GET', path: '/account/billing/invoices' });
|
|
326
|
+
break;
|
|
327
|
+
case 'portal':
|
|
328
|
+
data = await callApi({ method: 'GET', path: '/account/billing/portal' });
|
|
329
|
+
break;
|
|
330
|
+
case 'subscribe':
|
|
331
|
+
data = await callApi({ method: 'POST', path: '/account/billing/subscribe', body });
|
|
332
|
+
break;
|
|
333
|
+
case 'check_vat':
|
|
334
|
+
data = await callApi({ method: 'GET', path: '/account/billing/check-vat', params: filterParams({ vat_number }) });
|
|
335
|
+
break;
|
|
336
|
+
case 'address':
|
|
337
|
+
data = await callApi({ method: 'POST', path: '/account/billing/address', body });
|
|
338
|
+
break;
|
|
339
|
+
case 'setup':
|
|
340
|
+
data = await callApi({ method: 'POST', path: '/account/billing/setup' });
|
|
341
|
+
break;
|
|
342
|
+
case 'preview_change':
|
|
343
|
+
data = await callApi({ method: 'POST', path: '/account/billing/preview-change', body });
|
|
344
|
+
break;
|
|
345
|
+
case 'change_plan':
|
|
346
|
+
data = await callApi({ method: 'POST', path: '/account/billing/change-plan', body });
|
|
347
|
+
break;
|
|
348
|
+
case 'undo_cancellation':
|
|
349
|
+
data = await callApi({ method: 'POST', path: '/account/billing/undo-cancellation' });
|
|
350
|
+
break;
|
|
351
|
+
case 'delete_card':
|
|
352
|
+
data = await callApi({ method: 'DELETE', path: `/account/billing/cards/${card_id}` });
|
|
353
|
+
break;
|
|
354
|
+
default:
|
|
355
|
+
throw new Error(`Unknown billing action: ${action}`);
|
|
356
|
+
}
|
|
357
|
+
return unwrap(data);
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
// ─── Team ────────────────────────────────────────────────────────
|
|
361
|
+
{
|
|
362
|
+
name: 'manage_team',
|
|
363
|
+
description: 'Manage team members: list, edit roles, remove, invite, and manage invitations.',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
action: {
|
|
368
|
+
type: 'string',
|
|
369
|
+
enum: ['list', 'edit', 'remove', 'invite', 'revoke_invite', 'resend_invite'],
|
|
370
|
+
description: 'Team operation',
|
|
371
|
+
},
|
|
372
|
+
user_id: { type: 'string', description: 'User ID (for edit/remove)' },
|
|
373
|
+
email: { type: 'string', description: 'Email address (for revoke_invite/resend_invite)' },
|
|
374
|
+
body: { type: 'object', description: 'Request body (for edit: role; for invite: email, role)' },
|
|
375
|
+
},
|
|
376
|
+
required: ['action'],
|
|
377
|
+
},
|
|
378
|
+
handler: async (params) => {
|
|
379
|
+
const { action, user_id, email, body } = params;
|
|
380
|
+
let data;
|
|
381
|
+
switch (action) {
|
|
382
|
+
case 'list':
|
|
383
|
+
data = await callApi({ method: 'GET', path: '/account/team' });
|
|
384
|
+
break;
|
|
385
|
+
case 'edit':
|
|
386
|
+
data = await callApi({ method: 'PUT', path: `/account/team/${user_id}`, body });
|
|
387
|
+
break;
|
|
388
|
+
case 'remove':
|
|
389
|
+
data = await callApi({ method: 'DELETE', path: `/account/team/${user_id}` });
|
|
390
|
+
break;
|
|
391
|
+
case 'invite':
|
|
392
|
+
data = await callApi({ method: 'POST', path: '/account/team/invite', body });
|
|
393
|
+
break;
|
|
394
|
+
case 'revoke_invite':
|
|
395
|
+
data = await callApi({ method: 'DELETE', path: `/account/team/invites/${email}` });
|
|
396
|
+
break;
|
|
397
|
+
case 'resend_invite':
|
|
398
|
+
data = await callApi({ method: 'POST', path: `/account/team/resend/${email}` });
|
|
399
|
+
break;
|
|
400
|
+
default:
|
|
401
|
+
throw new Error(`Unknown team action: ${action}`);
|
|
402
|
+
}
|
|
403
|
+
return unwrap(data);
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
// ─── API Keys ────────────────────────────────────────────────────
|
|
407
|
+
{
|
|
408
|
+
name: 'manage_api_keys',
|
|
409
|
+
description: 'Manage API keys: list, create, update, and delete.',
|
|
410
|
+
inputSchema: {
|
|
411
|
+
type: 'object',
|
|
412
|
+
properties: {
|
|
413
|
+
action: { type: 'string', enum: ['list', 'create', 'update', 'delete'], description: 'API key operation' },
|
|
414
|
+
id: { type: 'string', description: 'API key ID (for update/delete)' },
|
|
415
|
+
body: { type: 'object', description: 'API key data (for create/update: name, permissions)' },
|
|
416
|
+
},
|
|
417
|
+
required: ['action'],
|
|
418
|
+
},
|
|
419
|
+
handler: async (params) => {
|
|
420
|
+
const { action, id, body } = params;
|
|
421
|
+
let data;
|
|
422
|
+
switch (action) {
|
|
423
|
+
case 'list':
|
|
424
|
+
data = await callApi({ method: 'GET', path: '/account/api-keys' });
|
|
425
|
+
break;
|
|
426
|
+
case 'create':
|
|
427
|
+
data = await callApi({ method: 'POST', path: '/account/api-keys', body });
|
|
428
|
+
break;
|
|
429
|
+
case 'update':
|
|
430
|
+
data = await callApi({ method: 'PUT', path: `/account/api-keys/${id}`, body });
|
|
431
|
+
break;
|
|
432
|
+
case 'delete':
|
|
433
|
+
data = await callApi({ method: 'DELETE', path: `/account/api-keys/${id}` });
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
throw new Error(`Unknown api-keys action: ${action}`);
|
|
437
|
+
}
|
|
438
|
+
return unwrap(data);
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
// ─── Generic Passthrough ─────────────────────────────────────────
|
|
442
|
+
{
|
|
443
|
+
name: 'api_request',
|
|
444
|
+
description: 'Generic API passthrough for any GrowPanel API endpoint. Use this for endpoints not covered by other tools, or when new API features are added. Any valid API path works — this tool makes the MCP server automatically support new features.',
|
|
445
|
+
inputSchema: {
|
|
446
|
+
type: 'object',
|
|
447
|
+
properties: {
|
|
448
|
+
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE'], description: 'HTTP method' },
|
|
449
|
+
path: { type: 'string', description: 'API path starting with / (e.g., /reports/mrr, /data/customers)' },
|
|
450
|
+
params: {
|
|
451
|
+
type: 'object',
|
|
452
|
+
description: 'Query parameters as key-value pairs (for GET requests)',
|
|
453
|
+
additionalProperties: { type: 'string' },
|
|
454
|
+
},
|
|
455
|
+
body: { type: 'object', description: 'Request body (for POST/PUT requests)' },
|
|
456
|
+
},
|
|
457
|
+
required: ['method', 'path'],
|
|
458
|
+
},
|
|
459
|
+
handler: async (params) => {
|
|
460
|
+
const { method, path, params: queryParams, body } = params;
|
|
461
|
+
const data = await callApi({
|
|
462
|
+
method,
|
|
463
|
+
path,
|
|
464
|
+
params: queryParams ? filterParams(queryParams) : undefined,
|
|
465
|
+
body,
|
|
466
|
+
});
|
|
467
|
+
return unwrap(data);
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@growpanel/mcp-server",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "MCP server for GrowPanel SaaS subscription analytics API - 14 tools covering reports, customers, settings, and more",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"growpanel-mcp": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/server.ts",
|
|
13
|
+
"start": "node dist/server.js",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@growpanel/sdk": "^0.1.4",
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.17.4",
|
|
22
|
+
"dotenv": "^16.3.1",
|
|
23
|
+
"zod": "^3.22.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.17.0",
|
|
27
|
+
"tsx": "^4.19.0",
|
|
28
|
+
"typescript": "^5.3.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist/**/*",
|
|
32
|
+
"bin/**/*",
|
|
33
|
+
"package.json",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"mcp",
|
|
38
|
+
"growpanel",
|
|
39
|
+
"ai-tools",
|
|
40
|
+
"saas-metrics",
|
|
41
|
+
"subscription-analytics",
|
|
42
|
+
"mrr",
|
|
43
|
+
"churn",
|
|
44
|
+
"recurring-revenue"
|
|
45
|
+
],
|
|
46
|
+
"author": "GrowPanel ApS",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/growpanel/growpanel-mcp-server.git"
|
|
51
|
+
}
|
|
52
|
+
}
|