@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 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
- # @openaisdk/billing-mcp
1
+ # `@openaisdk/billing-mcp` (billing-catalog MCP)
2
2
 
3
- MCP-сервер для **управления каталогом** в Billing API: проекты, **планы**, **цены** (REST `v1/projects/.../plans`, `.../prices`).
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` | да | Project-scoped integration key (получить через Admin UI Integration) |
10
- | `BILLING_PROJECT_ID` | да | UUID проекта (Admin UI Settings или `pnpm db:seed` output) |
11
- | `BILLING_API_BASE_URL` | нет | По умолчанию `http://127.0.0.1:4001` |
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
- Ключ передаётся как `Authorization: Bearer <key>`. `BILLING_TENANT_ID` / `DEV_TENANT_ID` больше не нужны.
107
+ Устаревший алиас: `BILLING_API_BASE_URL` (используется только если `BILLING_API_URL` пуст).
14
108
 
15
- ## Cursor
109
+ **Безопасность:** не коммитьте ключи. Для CI используйте секреты окружения.
16
110
 
17
- В `.cursor/mcp.json` добавьте сервер (после `pnpm install` и `pnpm --filter @openaisdk/billing-mcp run build`):
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
- "BILLING_API_BASE_URL": "http://127.0.0.1:4001",
28
- "BILLING_API_KEY": "<project-scoped integration key>",
29
- "BILLING_PROJECT_ID": "<project-uuid>"
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
- Для разработки без сборки можно `tsx`:
158
+ После изменения конфигурации перезапустите MCP в клиенте (в Cursor: Settings → MCP → Restart).
37
159
 
38
- ```json
39
- "command": "pnpm",
40
- "args": ["exec", "tsx", "packages/billing-catalog-mcp/index.ts"],
41
- "cwd": "${workspaceFolder}"
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
- - `billing_list_projects` проекты тенанта
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
- `billing_create_price`: укажите `planId` **или** `planCode` (например `team`), плюс `code`, `amountMinor` (копейки), `interval` (`month` | `year`).
170
+ ### Проекты
51
171
 
52
- ## Сборка
172
+ | Tool | Назначение |
173
+ |------|------------|
174
+ | `billing_list_projects` | `GET /v1/projects` — проекты tenant’а, доступные ключу. Аргументов нет. |
53
175
 
54
- ```bash
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
- ```bash
61
- BILLING_API_KEY=<key> BILLING_PROJECT_ID=<uuid> pnpm --filter @openaisdk/billing-mcp run dev
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
- Stdio ждёт MCP-клиент; для ручной проверки используйте Cursor или тестовый MCP-клиент.
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
- return (process.env.BILLING_API_BASE_URL ?? 'http://127.0.0.1:4001').replace(/\/$/, '');
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: { type: 'string', description: 'UUID проекта' },
110
+ projectId: SCHEMA_PROJECT_ID,
80
111
  },
81
- required: ['projectId'],
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: { type: 'string' },
121
+ projectId: SCHEMA_PROJECT_ID,
91
122
  planId: { type: 'string' },
92
123
  },
93
- required: ['projectId', 'planId'],
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: { type: 'string' },
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: ['projectId', 'code', 'name'],
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: { type: 'string' },
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: ['projectId', 'planId'],
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: { type: 'string' },
167
+ projectId: SCHEMA_PROJECT_ID,
137
168
  },
138
- required: ['projectId'],
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: { type: 'string' },
178
+ projectId: SCHEMA_PROJECT_ID,
148
179
  priceId: { type: 'string' },
149
180
  },
150
- required: ['projectId', 'priceId'],
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: { type: 'string' },
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: ['projectId', 'code', 'amountMinor', 'interval'],
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: { type: 'string' },
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: ['projectId', 'priceId'],
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: { type: 'string', description: 'UUID проекта (напр. megaretro)' },
229
+ projectId: SCHEMA_PROJECT_ID,
199
230
  },
200
- required: ['projectId'],
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: { type: 'string', description: 'UUID проекта megaretro' },
240
+ projectId: SCHEMA_PROJECT_ID,
210
241
  },
211
- required: ['projectId'],
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: { type: 'string', description: 'UUID проекта' },
251
+ projectId: SCHEMA_PROJECT_ID,
221
252
  },
222
- required: ['projectId'],
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: { type: 'string' } },
231
- required: ['projectId'],
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: { type: 'string' },
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: ['projectId', 'code', 'name', 'kind'],
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: { type: 'string' },
285
+ projectId: SCHEMA_PROJECT_ID,
255
286
  planId: { type: 'string' },
256
287
  planCode: { type: 'string', description: 'напр. team, business' },
257
288
  },
258
- required: ['projectId'],
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: { type: 'string' },
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: ['projectId', 'featureId'],
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, args) {
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.projectId}/plans`);
330
+ return billingFetch(`/v1/projects/${requireProjectId(args)}/plans`);
299
331
  case 'billing_get_plan':
300
- return billingFetch(`/v1/projects/${args.projectId}/plans/${args.planId}`);
332
+ return billingFetch(`/v1/projects/${requireProjectId(args)}/plans/${args.planId}`);
301
333
  case 'billing_create_plan':
302
- return billingFetch(`/v1/projects/${args.projectId}/plans`, {
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.projectId}/plans/${args.planId}`, {
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.projectId}/prices`);
344
+ return billingFetch(`/v1/projects/${requireProjectId(args)}/prices`);
313
345
  case 'billing_get_price':
314
- return billingFetch(`/v1/projects/${args.projectId}/prices/${args.priceId}`);
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(String(args.projectId), planCode);
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/${args.projectId}/prices`, {
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.projectId}/prices/${args.priceId}`, {
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.projectId}/reset-catalog`, {
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(String(args.projectId));
390
+ return seedMegaretroCatalog(requireProjectId(args));
358
391
  case 'billing_upsert_megaretro_features_and_limits':
359
- return upsertMegaretroFeaturesAndLimits(billingFetch, String(args.projectId));
392
+ return upsertMegaretroFeaturesAndLimits(billingFetch, requireProjectId(args));
360
393
  case 'billing_list_features':
361
- return billingFetch(`/v1/projects/${args.projectId}/features`);
394
+ return billingFetch(`/v1/projects/${requireProjectId(args)}/features`);
362
395
  case 'billing_create_feature':
363
- return billingFetch(`/v1/projects/${args.projectId}/features`, {
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 planId = await resolvePlanIdFromArgs(String(args.projectId), args);
369
- return billingFetch(`/v1/projects/${args.projectId}/plans/${planId}/plan-features`);
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 planId = await resolvePlanIdFromArgs(String(args.projectId), args);
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/${args.projectId}/plans/${planId}/plan-features`, {
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 (project-scoped key configured)');
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.2.1",
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"