@openaisdk/billing-mcp 0.2.1 → 1.0.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/.env.example +14 -0
- package/README.md +212 -30
- package/dist/index.js +88 -53
- package/package.json +4 -2
package/.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Договор имён — как у @openaisdk/billing-sdk (без завершающего /)
|
|
2
|
+
BILLING_API_URL=http://127.0.0.1:4001
|
|
3
|
+
|
|
4
|
+
# Project-scoped integration key (Admin UI → Интеграция или вывод pnpm db:seed)
|
|
5
|
+
BILLING_API_KEY=
|
|
6
|
+
|
|
7
|
+
# UUID проекта: подставляется в инструменты, если projectId не передан в аргументах
|
|
8
|
+
BILLING_PROJECT_ID=
|
|
9
|
+
|
|
10
|
+
# UUID tenant — заголовок x-tenant-id (как в OpenAPI/SDK); опционально при M2M-ключе
|
|
11
|
+
BILLING_HTTP_X_TENANT_ID=
|
|
12
|
+
|
|
13
|
+
# Устаревшее имя base URL (читается, если BILLING_API_URL не задан)
|
|
14
|
+
# BILLING_API_BASE_URL=http://127.0.0.1:4001
|
package/README.md
CHANGED
|
@@ -1,20 +1,141 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@openaisdk/billing-mcp` (billing-catalog MCP)
|
|
2
2
|
|
|
3
|
-
MCP
|
|
3
|
+
[MCP](https://modelcontextprotocol.io/)‑сервер по stdio для работы с **каталогом** Billing Platform через REST API: **проекты**, **планы**, **цены**, **фичи** и привязки фич к планам (`plan-features`). Подходит для Cursor, Claude Desktop и любых клиентов с поддержкой MCP.
|
|
4
|
+
|
|
5
|
+
**Пакет в репозитории:** `packages/billing-catalog-mcp`
|
|
6
|
+
**Имя на npm:** `@openaisdk/billing-mcp`
|
|
7
|
+
**Бинарь:** `billing-catalog-mcp`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Контекст и цель
|
|
12
|
+
|
|
13
|
+
В multi-tenant billing у **tenant** есть один или несколько **projects**. У каждого **project** свой каталог: **plans**, **prices**, **features**, **entitlements** (через подписки). Этот MCP не заменяет Admin UI и не обслуживает checkout — он даёт агентам и разработчикам **программный доступ к каталогу** того project, к которому привязан integration key.
|
|
14
|
+
|
|
15
|
+
**Зачем использовать MCP:** настройка демо-каталога, сценарии в IDE с ИИ, скриптоподобные операции без написания отдельного HTTP-клиента.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Аудитория
|
|
20
|
+
|
|
21
|
+
- Разработчики платформы и интеграторы, у которых уже есть доступ к Billing API.
|
|
22
|
+
- Команды, которые подключают MCP в Cursor/другом клиенте для работы с планами и ценами.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Термины
|
|
27
|
+
|
|
28
|
+
| Термин | Значение |
|
|
29
|
+
|--------|----------|
|
|
30
|
+
| **Tenant** | Изолированный арендатор платформы; определяется на стороне API по ключу. |
|
|
31
|
+
| **Project** | Продукт/линейка внутри tenant; у него свой каталог. `projectId` — UUID. |
|
|
32
|
+
| **Plan** | Тарифный план (код уникален в рамках project). |
|
|
33
|
+
| **Price** | Цена для плана (интервал `month` / `year`, сумма в минорных единицах). |
|
|
34
|
+
| **Feature** | Возможность или лимит (`boolean` / `limit`). |
|
|
35
|
+
| **Plan-feature** | Привязка фичи к плану с флагом и/или лимитом. |
|
|
36
|
+
| **Project-scoped API key** | Ключ M2M из Admin UI → Интеграция; даёт доступ только к данным своего project (см. ADR-010). |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Scope: что делает сервер
|
|
41
|
+
|
|
42
|
+
**Реализовано в коде:**
|
|
43
|
+
|
|
44
|
+
- Чтение списка **projects** tenant’а (в рамках прав ключа).
|
|
45
|
+
- CRUD **plans** и **prices** для выбранного `projectId`.
|
|
46
|
+
- Список/создание **features**, список/создание **plan-features**.
|
|
47
|
+
- Вспомогательные инструменты для **локальной разработки и пилотов**: сброс каталога проекта (через dev-эндпоинт API), сид MegaRetro, идемпотентное обновление фич/лимитов MegaRetro.
|
|
48
|
+
|
|
49
|
+
**Не входит в scope пакета:**
|
|
50
|
+
|
|
51
|
+
- Подписки, инвойсы, customer accounts, checkout, webhooks (для этого — Billing API/SDK/Admin UI).
|
|
52
|
+
- Обход аутентификации без ключа: модель **только Bearer project-scoped key** (см. ниже).
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Предпосылки
|
|
57
|
+
|
|
58
|
+
1. Развёрнутый **Billing API** (локально или удалённо).
|
|
59
|
+
2. **Project-scoped integration key** (`BILLING_API_KEY`): создаётся в **Admin UI → Интеграция** или выдаётся после `pnpm db:seed` в консоли (локальная разработка).
|
|
60
|
+
3. **UUID project** — в аргументах инструментов (`projectId`) **или** в переменной **`BILLING_PROJECT_ID`** (тот же договор, что у `@openaisdk/billing-sdk`).
|
|
61
|
+
|
|
62
|
+
**Auth (ADR-010):** `Authorization: Bearer <BILLING_API_KEY>`. Если задан **`BILLING_HTTP_X_TENANT_ID`**, в запросы добавляется заголовок `x-tenant-id` (как в OpenAPI/SDK). Tenant/project по-прежнему резолвятся из ключа на стороне API.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Установка
|
|
67
|
+
|
|
68
|
+
### Из npm (потребители вне монорепозитория)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm add -g @openaisdk/billing-mcp@latest
|
|
72
|
+
# или без глобальной установки — см. npx ниже
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Из монорепозитория `saas-billing`
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pnpm install
|
|
79
|
+
pnpm --filter @openaisdk/billing-mcp run build
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Запуск собранного сервера:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node packages/billing-catalog-mcp/dist/index.js
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Локальная отладка без сборки:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm --filter @openaisdk/billing-mcp run dev
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
4
95
|
|
|
5
96
|
## Переменные окружения
|
|
6
97
|
|
|
98
|
+
Сервер подгружает `.env` из **текущей рабочей директории** процесса (`dotenv/config`). В Cursor обычно задают `env` в конфиге MCP или `cwd` на корень проекта, где лежит `.env`.
|
|
99
|
+
|
|
7
100
|
| Переменная | Обязательно | Описание |
|
|
8
101
|
|------------|-------------|----------|
|
|
9
|
-
| `BILLING_API_KEY` |
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
102
|
+
| `BILLING_API_KEY` | **Да** | Project-scoped integration key (Bearer). Без него процесс завершится при старте. |
|
|
103
|
+
| `BILLING_API_URL` | Нет | Base URL Billing API **без** завершающего `/`. По умолчанию `http://127.0.0.1:4001`. |
|
|
104
|
+
| `BILLING_PROJECT_ID` | Нет | UUID проекта: подставляется, если в аргументе инструмента не передан `projectId`. |
|
|
105
|
+
| `BILLING_HTTP_X_TENANT_ID` | Нет | UUID tenant → заголовок `x-tenant-id` (согласовано с SDK / OpenAPI). |
|
|
12
106
|
|
|
13
|
-
|
|
107
|
+
Устаревший алиас: `BILLING_API_BASE_URL` (используется только если `BILLING_API_URL` пуст).
|
|
14
108
|
|
|
15
|
-
|
|
109
|
+
**Безопасность:** не коммитьте ключи. Для CI используйте секреты окружения.
|
|
16
110
|
|
|
17
|
-
|
|
111
|
+
Источник истины по именам — `.env.example` в каталоге пакета и `@openaisdk/billing-sdk` (те же четыре переменные).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Подключение MCP-клиента (пример Cursor)
|
|
116
|
+
|
|
117
|
+
Файл настроек: `.cursor/mcp.json` (или аналог в вашем клиенте).
|
|
118
|
+
|
|
119
|
+
### Вариант A: `npx` (после публикации пакета)
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"billing-catalog": {
|
|
125
|
+
"command": "npx",
|
|
126
|
+
"args": ["-y", "@openaisdk/billing-mcp@latest"],
|
|
127
|
+
"env": {
|
|
128
|
+
"BILLING_API_URL": "http://127.0.0.1:4001",
|
|
129
|
+
"BILLING_API_KEY": "<project-scoped-key>",
|
|
130
|
+
"BILLING_PROJECT_ID": "<project-uuid>",
|
|
131
|
+
"BILLING_HTTP_X_TENANT_ID": "<tenant-uuid>"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Вариант B: сборка из клонированного репозитория
|
|
18
139
|
|
|
19
140
|
```json
|
|
20
141
|
{
|
|
@@ -24,41 +145,102 @@ MCP-сервер для **управления каталогом** в Billing A
|
|
|
24
145
|
"args": ["packages/billing-catalog-mcp/dist/index.js"],
|
|
25
146
|
"cwd": "${workspaceFolder}",
|
|
26
147
|
"env": {
|
|
27
|
-
"
|
|
28
|
-
"BILLING_API_KEY": "
|
|
29
|
-
"BILLING_PROJECT_ID": "
|
|
148
|
+
"BILLING_API_URL": "http://127.0.0.1:4001",
|
|
149
|
+
"BILLING_API_KEY": "<из вывода pnpm db:seed или Admin UI>",
|
|
150
|
+
"BILLING_PROJECT_ID": "<из seed / Admin UI>",
|
|
151
|
+
"BILLING_HTTP_X_TENANT_ID": "<из seed / Admin UI>"
|
|
30
152
|
}
|
|
31
153
|
}
|
|
32
154
|
}
|
|
33
155
|
}
|
|
34
156
|
```
|
|
35
157
|
|
|
36
|
-
|
|
158
|
+
После изменения конфигурации перезапустите MCP в клиенте (в Cursor: Settings → MCP → Restart).
|
|
37
159
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
160
|
+
**Локальный быстрый старт credentials:** `pnpm db:seed` — в консоли появятся в том числе project UUID и API key; подставьте их в `env` и используйте UUID как `projectId` в инструментах.
|
|
161
|
+
|
|
162
|
+
Дополнительно по нескольким MCP-серверам репозитория: [docs/mcp-integration-guide.md](../../docs/mcp-integration-guide.md).
|
|
163
|
+
|
|
164
|
+
---
|
|
43
165
|
|
|
44
166
|
## Инструменты (tools)
|
|
45
167
|
|
|
46
|
-
|
|
47
|
-
- `billing_list_plans` / `billing_get_plan` / `billing_create_plan` / `billing_update_plan`
|
|
48
|
-
- `billing_list_prices` / `billing_get_price` / `billing_create_price` / `billing_update_price`
|
|
168
|
+
Ответы приходят как **JSON-текст** в content MCP. При ошибке HTTP или валидации клиент получит сообщение с префиксом `Error: ...`.
|
|
49
169
|
|
|
50
|
-
|
|
170
|
+
### Проекты
|
|
51
171
|
|
|
52
|
-
|
|
172
|
+
| Tool | Назначение |
|
|
173
|
+
|------|------------|
|
|
174
|
+
| `billing_list_projects` | `GET /v1/projects` — проекты tenant’а, доступные ключу. Аргументов нет. |
|
|
53
175
|
|
|
54
|
-
|
|
55
|
-
pnpm --filter @openaisdk/billing-mcp run build
|
|
56
|
-
```
|
|
176
|
+
### Планы
|
|
57
177
|
|
|
58
|
-
|
|
178
|
+
| Tool | Назначение |
|
|
179
|
+
|------|------------|
|
|
180
|
+
| `billing_list_plans` | Список планов. Нужен `projectId`. |
|
|
181
|
+
| `billing_get_plan` | План по ID: `projectId`, `planId`. |
|
|
182
|
+
| `billing_create_plan` | Создание: `projectId`, `code`, `name`; опционально `description`, `isPublic`, `isActive`, `sortOrder`. |
|
|
183
|
+
| `billing_update_plan` | PATCH: `projectId`, `planId`; поля плана кроме `code`. |
|
|
59
184
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
185
|
+
### Цены
|
|
186
|
+
|
|
187
|
+
| Tool | Назначение |
|
|
188
|
+
|------|------------|
|
|
189
|
+
| `billing_list_prices` | Список цен проекта. |
|
|
190
|
+
| `billing_get_price` | Цена по ID: `projectId`, `priceId`. |
|
|
191
|
+
| `billing_create_price` | Создание: `projectId`, `code`, `amountMinor`, `interval` (`month` \| `year`). Нужен **`planId` или `planCode`**. Опционально: `currency` (по умолчанию `RUB`), `intervalCount`, `trialDays`, `isActive`. |
|
|
192
|
+
| `billing_update_price` | PATCH: `projectId`, `priceId`. Поля: `code`, `amountMinor`, `currency`, `interval`, `intervalCount`, `trialDays`, `isActive`. План сменить нельзя. |
|
|
193
|
+
|
|
194
|
+
### Фичи и plan-features
|
|
195
|
+
|
|
196
|
+
| Tool | Назначение |
|
|
197
|
+
|------|------------|
|
|
198
|
+
| `billing_list_features` | Список фич проекта. |
|
|
199
|
+
| `billing_create_feature` | Создать фичу: `projectId`, `code`, `name`, `kind` (`boolean` \| `limit`). |
|
|
200
|
+
| `billing_list_plan_features` | Привязки фич к плану. Нужны `projectId` и **`planId` или `planCode`**. |
|
|
201
|
+
| `billing_create_plan_feature` | Привязать фичу к плану: `projectId`, `featureId`, плюс **`planId` или `planCode`**. Для лимитов: `limitValue` (для безлимита — `null` в JSON аргумента); для boolean — `enabled`. |
|
|
202
|
+
|
|
203
|
+
### Разработка и сиды MegaRetro
|
|
204
|
+
|
|
205
|
+
| Tool | Назначение |
|
|
206
|
+
|------|------------|
|
|
207
|
+
| `billing_reset_project_catalog` | `POST /v1/dev/projects/:projectId/reset-catalog` — жёсткий сброс каталога и связанных billing-сущностей проекта. На стороне API действует **DevOnlyGuard**: в **production** недоступно. Не удаляет customer accounts и сам project. |
|
|
208
|
+
| `billing_reset_and_seed_megaretro_catalog` | Сброс проекта (как выше), затем полный сид каталога MegaRetro (планы, цены, trial, фичи, add-on и т.д. по внутреннему канону репозитория). |
|
|
209
|
+
| `billing_upsert_megaretro_features_and_limits` | Без сброса: идемпотентно создаёт недостающие фичи и привязки для планов free/team/business/enterprise. |
|
|
210
|
+
|
|
211
|
+
Инструменты с «MegaRetro» в названии заточены под **пилотный каталог** первого consumer’а; для других продуктов используйте обычные CRUD-инструменты.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Ошибки и ограничения
|
|
216
|
+
|
|
217
|
+
- **Нет ключа:** при старте — ошибка с текстом про `BILLING_API_KEY`.
|
|
218
|
+
- **HTTP-ошибки API:** возвращаются как `Error: HTTP <status>: <body>`.
|
|
219
|
+
- **Dev reset:** эндпоинт `/v1/dev/...` отключён в production-сборке API; не рассчитывайте на сброс в бою.
|
|
220
|
+
- **Суммы:** `amountMinor` — в **минорных единицах** (копейки для RUB).
|
|
221
|
+
- **`planCode`:** резолвится через список планов проекта; при опечатке будет ошибка «план не найден».
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Верификация (чеклист)
|
|
226
|
+
|
|
227
|
+
- [ ] Billing API доступен по `BILLING_API_URL` (или дефолт localhost).
|
|
228
|
+
- [ ] `BILLING_API_KEY` — действующий project-scoped key; при необходимости заданы `BILLING_PROJECT_ID` / `BILLING_HTTP_X_TENANT_ID`.
|
|
229
|
+
- [ ] Вызов `billing_list_projects` возвращает JSON со списком проектов.
|
|
230
|
+
- [ ] Вызов `billing_list_plans` с правильным `projectId` возвращает планы (или пустой массив).
|
|
231
|
+
- [ ] После изменения `mcp.json` MCP перезапущен.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Ссылки
|
|
236
|
+
|
|
237
|
+
- [MCP Integration Guide](../../docs/mcp-integration-guide.md) — обзор MCP в репозитории и auth после ADR-010.
|
|
238
|
+
- [ADR-010: Project-scoped Integration Auth](../../docs/adr/ADR-010-project-scoped-integration-auth.md).
|
|
239
|
+
- [Consumer integration guide](../../docs/consumer-integration-guide.md) — M2M-интеграция приложений (шире, чем MCP).
|
|
240
|
+
- Публичные контракты API: [docs/api/public-api-v1.md](../../docs/api/public-api-v1.md), [docs/api/admin-api.md](../../docs/api/admin-api.md) (актуальные пути уточняйте по OpenAPI/Swagger вашего стенда).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Лицензия
|
|
63
245
|
|
|
64
|
-
|
|
246
|
+
MIT (см. `package.json`).
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,22 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
9
|
import { seedMegaretroCatalog as runMegaretroCatalogSeed, upsertMegaretroFeaturesAndLimits, } from './megaretro-catalog-seed.js';
|
|
10
10
|
function getBaseUrl() {
|
|
11
|
-
|
|
11
|
+
const raw = process.env.BILLING_API_URL?.trim() ||
|
|
12
|
+
process.env.BILLING_API_BASE_URL?.trim() ||
|
|
13
|
+
'http://127.0.0.1:4001';
|
|
14
|
+
return raw.replace(/\/$/, '');
|
|
12
15
|
}
|
|
13
16
|
function buildHeaders(extra) {
|
|
14
17
|
const h = { ...extra };
|
|
15
|
-
// Auth: project-scoped integration key (ADR-010).
|
|
16
|
-
// Tenant и project резолвятся из ключа на стороне API — x-tenant-id не нужен.
|
|
17
18
|
const key = process.env.BILLING_API_KEY?.trim();
|
|
18
19
|
if (!key) {
|
|
19
20
|
throw new Error('Задайте BILLING_API_KEY в .env / env MCP (project-scoped integration key, создаётся в Admin UI → Интеграция)');
|
|
20
21
|
}
|
|
21
22
|
h['Authorization'] = `Bearer ${key}`;
|
|
23
|
+
const tenantId = process.env.BILLING_HTTP_X_TENANT_ID?.trim();
|
|
24
|
+
if (tenantId) {
|
|
25
|
+
h['x-tenant-id'] = tenantId;
|
|
26
|
+
}
|
|
22
27
|
return h;
|
|
23
28
|
}
|
|
24
29
|
async function billingFetch(path, init = {}) {
|
|
@@ -64,6 +69,32 @@ async function resolvePlanIdFromArgs(projectId, args) {
|
|
|
64
69
|
}
|
|
65
70
|
return planId;
|
|
66
71
|
}
|
|
72
|
+
/** Согласовано с @openaisdk/billing-sdk: BILLING_PROJECT_ID в env подставляется, если аргумент пустой. */
|
|
73
|
+
const SCHEMA_PROJECT_ID = {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'UUID проекта. Если не передан — используется BILLING_PROJECT_ID из окружения (как в billing-sdk).',
|
|
76
|
+
};
|
|
77
|
+
function mergeDefaultProjectId(toolName, raw) {
|
|
78
|
+
if (toolName === 'billing_list_projects') {
|
|
79
|
+
return { ...raw };
|
|
80
|
+
}
|
|
81
|
+
const pid = raw.projectId;
|
|
82
|
+
if (pid != null && String(pid).trim() !== '') {
|
|
83
|
+
return { ...raw };
|
|
84
|
+
}
|
|
85
|
+
const envPid = process.env.BILLING_PROJECT_ID?.trim();
|
|
86
|
+
if (envPid) {
|
|
87
|
+
return { ...raw, projectId: envPid };
|
|
88
|
+
}
|
|
89
|
+
return { ...raw };
|
|
90
|
+
}
|
|
91
|
+
function requireProjectId(args) {
|
|
92
|
+
const p = args.projectId;
|
|
93
|
+
if (p != null && String(p).trim() !== '') {
|
|
94
|
+
return String(p);
|
|
95
|
+
}
|
|
96
|
+
throw new Error('Укажите projectId в аргументах инструмента или задайте BILLING_PROJECT_ID в окружении (те же имена переменных, что у @openaisdk/billing-sdk).');
|
|
97
|
+
}
|
|
67
98
|
const tools = [
|
|
68
99
|
{
|
|
69
100
|
name: 'billing_list_projects',
|
|
@@ -76,9 +107,9 @@ const tools = [
|
|
|
76
107
|
inputSchema: {
|
|
77
108
|
type: 'object',
|
|
78
109
|
properties: {
|
|
79
|
-
projectId:
|
|
110
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
80
111
|
},
|
|
81
|
-
required: [
|
|
112
|
+
required: [],
|
|
82
113
|
},
|
|
83
114
|
},
|
|
84
115
|
{
|
|
@@ -87,10 +118,10 @@ const tools = [
|
|
|
87
118
|
inputSchema: {
|
|
88
119
|
type: 'object',
|
|
89
120
|
properties: {
|
|
90
|
-
projectId:
|
|
121
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
91
122
|
planId: { type: 'string' },
|
|
92
123
|
},
|
|
93
|
-
required: ['
|
|
124
|
+
required: ['planId'],
|
|
94
125
|
},
|
|
95
126
|
},
|
|
96
127
|
{
|
|
@@ -99,7 +130,7 @@ const tools = [
|
|
|
99
130
|
inputSchema: {
|
|
100
131
|
type: 'object',
|
|
101
132
|
properties: {
|
|
102
|
-
projectId:
|
|
133
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
103
134
|
code: { type: 'string', description: 'slug, напр. team, business' },
|
|
104
135
|
name: { type: 'string' },
|
|
105
136
|
description: { type: 'string', description: 'опционально' },
|
|
@@ -107,7 +138,7 @@ const tools = [
|
|
|
107
138
|
isActive: { type: 'boolean', description: 'по умолчанию true' },
|
|
108
139
|
sortOrder: { type: 'number', description: 'по умолчанию 0' },
|
|
109
140
|
},
|
|
110
|
-
required: ['
|
|
141
|
+
required: ['code', 'name'],
|
|
111
142
|
},
|
|
112
143
|
},
|
|
113
144
|
{
|
|
@@ -116,7 +147,7 @@ const tools = [
|
|
|
116
147
|
inputSchema: {
|
|
117
148
|
type: 'object',
|
|
118
149
|
properties: {
|
|
119
|
-
projectId:
|
|
150
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
120
151
|
planId: { type: 'string' },
|
|
121
152
|
name: { type: 'string' },
|
|
122
153
|
description: { type: 'string' },
|
|
@@ -124,7 +155,7 @@ const tools = [
|
|
|
124
155
|
isActive: { type: 'boolean' },
|
|
125
156
|
sortOrder: { type: 'number' },
|
|
126
157
|
},
|
|
127
|
-
required: ['
|
|
158
|
+
required: ['planId'],
|
|
128
159
|
},
|
|
129
160
|
},
|
|
130
161
|
{
|
|
@@ -133,9 +164,9 @@ const tools = [
|
|
|
133
164
|
inputSchema: {
|
|
134
165
|
type: 'object',
|
|
135
166
|
properties: {
|
|
136
|
-
projectId:
|
|
167
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
137
168
|
},
|
|
138
|
-
required: [
|
|
169
|
+
required: [],
|
|
139
170
|
},
|
|
140
171
|
},
|
|
141
172
|
{
|
|
@@ -144,10 +175,10 @@ const tools = [
|
|
|
144
175
|
inputSchema: {
|
|
145
176
|
type: 'object',
|
|
146
177
|
properties: {
|
|
147
|
-
projectId:
|
|
178
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
148
179
|
priceId: { type: 'string' },
|
|
149
180
|
},
|
|
150
|
-
required: ['
|
|
181
|
+
required: ['priceId'],
|
|
151
182
|
},
|
|
152
183
|
},
|
|
153
184
|
{
|
|
@@ -156,7 +187,7 @@ const tools = [
|
|
|
156
187
|
inputSchema: {
|
|
157
188
|
type: 'object',
|
|
158
189
|
properties: {
|
|
159
|
-
projectId:
|
|
190
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
160
191
|
planId: { type: 'string', description: 'UUID плана (если нет planCode)' },
|
|
161
192
|
planCode: { type: 'string', description: 'код плана, напр. team' },
|
|
162
193
|
code: { type: 'string', description: 'уникальный код цены в проекте, напр. team_monthly' },
|
|
@@ -167,7 +198,7 @@ const tools = [
|
|
|
167
198
|
trialDays: { type: 'number' },
|
|
168
199
|
isActive: { type: 'boolean', description: 'по умолчанию true' },
|
|
169
200
|
},
|
|
170
|
-
required: ['
|
|
201
|
+
required: ['code', 'amountMinor', 'interval'],
|
|
171
202
|
},
|
|
172
203
|
},
|
|
173
204
|
{
|
|
@@ -176,7 +207,7 @@ const tools = [
|
|
|
176
207
|
inputSchema: {
|
|
177
208
|
type: 'object',
|
|
178
209
|
properties: {
|
|
179
|
-
projectId:
|
|
210
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
180
211
|
priceId: { type: 'string' },
|
|
181
212
|
code: { type: 'string' },
|
|
182
213
|
amountMinor: { type: 'number' },
|
|
@@ -186,7 +217,7 @@ const tools = [
|
|
|
186
217
|
trialDays: { type: 'number' },
|
|
187
218
|
isActive: { type: 'boolean' },
|
|
188
219
|
},
|
|
189
|
-
required: ['
|
|
220
|
+
required: ['priceId'],
|
|
190
221
|
},
|
|
191
222
|
},
|
|
192
223
|
{
|
|
@@ -195,9 +226,9 @@ const tools = [
|
|
|
195
226
|
inputSchema: {
|
|
196
227
|
type: 'object',
|
|
197
228
|
properties: {
|
|
198
|
-
projectId:
|
|
229
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
199
230
|
},
|
|
200
|
-
required: [
|
|
231
|
+
required: [],
|
|
201
232
|
},
|
|
202
233
|
},
|
|
203
234
|
{
|
|
@@ -206,9 +237,9 @@ const tools = [
|
|
|
206
237
|
inputSchema: {
|
|
207
238
|
type: 'object',
|
|
208
239
|
properties: {
|
|
209
|
-
projectId:
|
|
240
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
210
241
|
},
|
|
211
|
-
required: [
|
|
242
|
+
required: [],
|
|
212
243
|
},
|
|
213
244
|
},
|
|
214
245
|
{
|
|
@@ -217,9 +248,9 @@ const tools = [
|
|
|
217
248
|
inputSchema: {
|
|
218
249
|
type: 'object',
|
|
219
250
|
properties: {
|
|
220
|
-
projectId:
|
|
251
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
221
252
|
},
|
|
222
|
-
required: [
|
|
253
|
+
required: [],
|
|
223
254
|
},
|
|
224
255
|
},
|
|
225
256
|
{
|
|
@@ -227,8 +258,8 @@ const tools = [
|
|
|
227
258
|
description: 'Список фич проекта (GET /v1/projects/:id/features)',
|
|
228
259
|
inputSchema: {
|
|
229
260
|
type: 'object',
|
|
230
|
-
properties: { projectId:
|
|
231
|
-
required: [
|
|
261
|
+
properties: { projectId: SCHEMA_PROJECT_ID },
|
|
262
|
+
required: [],
|
|
232
263
|
},
|
|
233
264
|
},
|
|
234
265
|
{
|
|
@@ -237,12 +268,12 @@ const tools = [
|
|
|
237
268
|
inputSchema: {
|
|
238
269
|
type: 'object',
|
|
239
270
|
properties: {
|
|
240
|
-
projectId:
|
|
271
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
241
272
|
code: { type: 'string' },
|
|
242
273
|
name: { type: 'string' },
|
|
243
274
|
kind: { type: 'string', enum: ['boolean', 'limit'] },
|
|
244
275
|
},
|
|
245
|
-
required: ['
|
|
276
|
+
required: ['code', 'name', 'kind'],
|
|
246
277
|
},
|
|
247
278
|
},
|
|
248
279
|
{
|
|
@@ -251,11 +282,11 @@ const tools = [
|
|
|
251
282
|
inputSchema: {
|
|
252
283
|
type: 'object',
|
|
253
284
|
properties: {
|
|
254
|
-
projectId:
|
|
285
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
255
286
|
planId: { type: 'string' },
|
|
256
287
|
planCode: { type: 'string', description: 'напр. team, business' },
|
|
257
288
|
},
|
|
258
|
-
required: [
|
|
289
|
+
required: [],
|
|
259
290
|
},
|
|
260
291
|
},
|
|
261
292
|
{
|
|
@@ -264,7 +295,7 @@ const tools = [
|
|
|
264
295
|
inputSchema: {
|
|
265
296
|
type: 'object',
|
|
266
297
|
properties: {
|
|
267
|
-
projectId:
|
|
298
|
+
projectId: SCHEMA_PROJECT_ID,
|
|
268
299
|
planId: { type: 'string' },
|
|
269
300
|
planCode: { type: 'string' },
|
|
270
301
|
featureId: { type: 'string' },
|
|
@@ -274,7 +305,7 @@ const tools = [
|
|
|
274
305
|
description: 'для kind=limit; для безлимита передайте null через сырой JSON аргумента',
|
|
275
306
|
},
|
|
276
307
|
},
|
|
277
|
-
required: ['
|
|
308
|
+
required: ['featureId'],
|
|
278
309
|
},
|
|
279
310
|
},
|
|
280
311
|
];
|
|
@@ -290,33 +321,35 @@ function pickBody(args, keys) {
|
|
|
290
321
|
}
|
|
291
322
|
return out;
|
|
292
323
|
}
|
|
293
|
-
async function handleTool(name,
|
|
324
|
+
async function handleTool(name, rawArgs) {
|
|
325
|
+
const args = mergeDefaultProjectId(name, rawArgs);
|
|
294
326
|
switch (name) {
|
|
295
327
|
case 'billing_list_projects':
|
|
296
328
|
return billingFetch('/v1/projects');
|
|
297
329
|
case 'billing_list_plans':
|
|
298
|
-
return billingFetch(`/v1/projects/${args
|
|
330
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/plans`);
|
|
299
331
|
case 'billing_get_plan':
|
|
300
|
-
return billingFetch(`/v1/projects/${args
|
|
332
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/plans/${args.planId}`);
|
|
301
333
|
case 'billing_create_plan':
|
|
302
|
-
return billingFetch(`/v1/projects/${args
|
|
334
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/plans`, {
|
|
303
335
|
method: 'POST',
|
|
304
336
|
body: JSON.stringify(pickBody(args, ['code', 'name', 'description', 'isPublic', 'isActive', 'sortOrder'])),
|
|
305
337
|
});
|
|
306
338
|
case 'billing_update_plan':
|
|
307
|
-
return billingFetch(`/v1/projects/${args
|
|
339
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/plans/${args.planId}`, {
|
|
308
340
|
method: 'PATCH',
|
|
309
341
|
body: JSON.stringify(pickBody(args, ['name', 'description', 'isPublic', 'isActive', 'sortOrder'])),
|
|
310
342
|
});
|
|
311
343
|
case 'billing_list_prices':
|
|
312
|
-
return billingFetch(`/v1/projects/${args
|
|
344
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/prices`);
|
|
313
345
|
case 'billing_get_price':
|
|
314
|
-
return billingFetch(`/v1/projects/${args
|
|
346
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/prices/${args.priceId}`);
|
|
315
347
|
case 'billing_create_price': {
|
|
348
|
+
const projectId = requireProjectId(args);
|
|
316
349
|
let planId = args.planId;
|
|
317
350
|
const planCode = args.planCode;
|
|
318
351
|
if (!planId && planCode) {
|
|
319
|
-
planId = await resolvePlanId(
|
|
352
|
+
planId = await resolvePlanId(projectId, planCode);
|
|
320
353
|
}
|
|
321
354
|
if (!planId) {
|
|
322
355
|
throw new Error('Укажите planId или planCode');
|
|
@@ -331,13 +364,13 @@ async function handleTool(name, args) {
|
|
|
331
364
|
trialDays: args.trialDays,
|
|
332
365
|
isActive: args.isActive ?? true,
|
|
333
366
|
};
|
|
334
|
-
return billingFetch(`/v1/projects/${
|
|
367
|
+
return billingFetch(`/v1/projects/${projectId}/prices`, {
|
|
335
368
|
method: 'POST',
|
|
336
369
|
body: JSON.stringify(body),
|
|
337
370
|
});
|
|
338
371
|
}
|
|
339
372
|
case 'billing_update_price':
|
|
340
|
-
return billingFetch(`/v1/projects/${args
|
|
373
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/prices/${args.priceId}`, {
|
|
341
374
|
method: 'PATCH',
|
|
342
375
|
body: JSON.stringify(pickBody(args, [
|
|
343
376
|
'code',
|
|
@@ -350,26 +383,28 @@ async function handleTool(name, args) {
|
|
|
350
383
|
])),
|
|
351
384
|
});
|
|
352
385
|
case 'billing_reset_project_catalog':
|
|
353
|
-
return billingFetch(`/v1/dev/projects/${args
|
|
386
|
+
return billingFetch(`/v1/dev/projects/${requireProjectId(args)}/reset-catalog`, {
|
|
354
387
|
method: 'POST',
|
|
355
388
|
});
|
|
356
389
|
case 'billing_reset_and_seed_megaretro_catalog':
|
|
357
|
-
return seedMegaretroCatalog(
|
|
390
|
+
return seedMegaretroCatalog(requireProjectId(args));
|
|
358
391
|
case 'billing_upsert_megaretro_features_and_limits':
|
|
359
|
-
return upsertMegaretroFeaturesAndLimits(billingFetch,
|
|
392
|
+
return upsertMegaretroFeaturesAndLimits(billingFetch, requireProjectId(args));
|
|
360
393
|
case 'billing_list_features':
|
|
361
|
-
return billingFetch(`/v1/projects/${args
|
|
394
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/features`);
|
|
362
395
|
case 'billing_create_feature':
|
|
363
|
-
return billingFetch(`/v1/projects/${args
|
|
396
|
+
return billingFetch(`/v1/projects/${requireProjectId(args)}/features`, {
|
|
364
397
|
method: 'POST',
|
|
365
398
|
body: JSON.stringify(pickBody(args, ['code', 'name', 'kind'])),
|
|
366
399
|
});
|
|
367
400
|
case 'billing_list_plan_features': {
|
|
368
|
-
const
|
|
369
|
-
|
|
401
|
+
const projectId = requireProjectId(args);
|
|
402
|
+
const planId = await resolvePlanIdFromArgs(projectId, args);
|
|
403
|
+
return billingFetch(`/v1/projects/${projectId}/plans/${planId}/plan-features`);
|
|
370
404
|
}
|
|
371
405
|
case 'billing_create_plan_feature': {
|
|
372
|
-
const
|
|
406
|
+
const projectId = requireProjectId(args);
|
|
407
|
+
const planId = await resolvePlanIdFromArgs(projectId, args);
|
|
373
408
|
const body = { featureId: args.featureId };
|
|
374
409
|
if (args.enabled !== undefined) {
|
|
375
410
|
body.enabled = args.enabled;
|
|
@@ -377,7 +412,7 @@ async function handleTool(name, args) {
|
|
|
377
412
|
if (Object.prototype.hasOwnProperty.call(args, 'limitValue')) {
|
|
378
413
|
body.limitValue = args.limitValue;
|
|
379
414
|
}
|
|
380
|
-
return billingFetch(`/v1/projects/${
|
|
415
|
+
return billingFetch(`/v1/projects/${projectId}/plans/${planId}/plan-features`, {
|
|
381
416
|
method: 'POST',
|
|
382
417
|
body: JSON.stringify(body),
|
|
383
418
|
});
|
|
@@ -410,7 +445,7 @@ async function main() {
|
|
|
410
445
|
buildHeaders();
|
|
411
446
|
const transport = new StdioServerTransport();
|
|
412
447
|
await server.connect(transport);
|
|
413
|
-
console.error('Billing Catalog MCP on stdio (
|
|
448
|
+
console.error('Billing Catalog MCP on stdio (BILLING_API_KEY; base URL BILLING_API_URL; optional BILLING_PROJECT_ID, BILLING_HTTP_X_TENANT_ID)');
|
|
414
449
|
}
|
|
415
450
|
main().catch((e) => {
|
|
416
451
|
console.error(e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openaisdk/billing-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP server: управление планами и ценами Billing API (catalog)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"billing-catalog-mcp": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
".env.example"
|
|
12
14
|
],
|
|
13
15
|
"publishConfig": {
|
|
14
16
|
"access": "public"
|