hamravesh-mcp 0.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/.env.example +19 -0
- package/CHANGELOG.md +16 -0
- package/ENDPOINTS-RAW.txt +578 -0
- package/ENDPOINTS.md +245 -0
- package/LICENSE +21 -0
- package/LIVE-STATUS.md +79 -0
- package/README.md +188 -0
- package/WRITE-ENDPOINTS.md +264 -0
- package/package.json +58 -0
- package/src/client.js +181 -0
- package/src/index.js +546 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Hamravesh — بدنهی درخواست write ها
|
|
2
|
+
|
|
3
|
+
> استخراجشده از کد پنل کنسول همروش (۱۴۰۵/۰۳/۲۳ — 2026-06-13).
|
|
4
|
+
> ستون «Body» شکل بدنهی JSON است که فرانت میفرستد. متغیرهای تکحرفی (مثل `t`,`i`,`e`) یعنی
|
|
5
|
+
> یک آبجکت/مقدار که از ورودی کاربر میآید. `{paas}`=`darkube`.
|
|
6
|
+
|
|
7
|
+
> 🔐 احراز هویت: `Authorization: Api-Key <key>` + `X-Organization: <org>` (یا `Bearer <jwt>`).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⭐ ساخت اپ داکر-ایمیج — بدنهی تأییدشده با تست زنده (2026-06-13)
|
|
12
|
+
|
|
13
|
+
این بدنه با ضبط از پنل + اجرای واقعی (CREATE→201، RESTART→200، DELETE→204) تأیید شده است:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
POST /api/v1/darkube/apps/
|
|
17
|
+
{
|
|
18
|
+
"image_repo": "traefik/whoami",
|
|
19
|
+
"image_tag": "latest",
|
|
20
|
+
"builder": "dockerfile",
|
|
21
|
+
"creation_method": "docker_image",
|
|
22
|
+
"name": "<نام یکتا، بعداً غیرقابلتغییر>",
|
|
23
|
+
"svc": {"type":"ClusterIP","ports":{"main":{"protocol":"TCP","servicePort":80,"containerPort":80}}},
|
|
24
|
+
"command": "",
|
|
25
|
+
"args": "",
|
|
26
|
+
"readiness_probe_path": "",
|
|
27
|
+
"custom_config": {},
|
|
28
|
+
"plan": "<uuid پلن منابع>",
|
|
29
|
+
"replicas": 1,
|
|
30
|
+
"backup_config": null,
|
|
31
|
+
"namespace": <id عددی namespace>,
|
|
32
|
+
"deploy_context": null,
|
|
33
|
+
"ssl_challenge_type": "dns01",
|
|
34
|
+
"organization": <id عددی سازمان>
|
|
35
|
+
}
|
|
36
|
+
→ 201 { "id": "<uuid اپ>", ... }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- مقادیر **wgcup**: `organization=24772`، `namespace=165693`، پلن کوچک `plan=73904144-6592-40ce-a96f-3789f611b5e4` (۵۰۰MB/0.25core).
|
|
40
|
+
- یک pre-step هم هست: `POST /api/v1/darkube/apps/schedule_info/` (پنل قبل از create صدا میزند؛ برای create لازم نیست).
|
|
41
|
+
- **چرخهی عمر تأییدشده:** `POST .../apps/{id}/restart/` با `{}` → 200 ؛ `DELETE .../apps/{id}/` (بدون بدنه) → 204.
|
|
42
|
+
- این دقیقاً همان کاری است که ابزار `hamravesh_create_app` در MCP انجام میدهد.
|
|
43
|
+
|
|
44
|
+
## Auth & API Keys
|
|
45
|
+
|
|
46
|
+
| Method | Path | Body |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| POST | `/api/v1/2fa/disable/` | `e` |
|
|
49
|
+
| POST | `/api/v1/2fa/enable/` | `e` |
|
|
50
|
+
| POST | `/api/v1/account/cluster/cluster_ingress_host_name/` | `e` |
|
|
51
|
+
| POST | `/api/v1/apikeys/` | `e` |
|
|
52
|
+
| DELETE | `/api/v1/apikeys/{id}/` | `e` |
|
|
53
|
+
| POST | `/api/v1/oauth/auth/` | `t` |
|
|
54
|
+
| POST | `/api/v1/token/` | `a` |
|
|
55
|
+
| POST | `/api/v1/token/refresh/` | `{refresh:a}` |
|
|
56
|
+
| POST | `/api/v1/users/change_password/` | `e` |
|
|
57
|
+
| POST | `/api/v1/users/forget_password/` | `e` |
|
|
58
|
+
| POST | `/api/v1/users/logout` | `{token:a,organization_id:t}` |
|
|
59
|
+
| PUT | `/api/v1/users/profile` | `e` |
|
|
60
|
+
| POST | `/api/v1/users/reset_password/` | `e` |
|
|
61
|
+
| PUT | `/api/v2/users/profile` | `e` |
|
|
62
|
+
| POST | `/api/v2/users/register_user/` | `e` |
|
|
63
|
+
| POST | `/api/v2/users/resend_verification_email/` | `e` |
|
|
64
|
+
| POST | `/api/v2/users/send_verification_sms/` | `e` |
|
|
65
|
+
| POST | `/api/v2/users/update_email/` | `e` |
|
|
66
|
+
| POST | `/api/v2/users/verify_email/` | `e` |
|
|
67
|
+
| POST | `/api/v2/users/verify_mobile/` | `e` |
|
|
68
|
+
| POST | `/api/v2/users/verify_mobile_national_id/` | `e` |
|
|
69
|
+
|
|
70
|
+
## PaaS / Darkube apps
|
|
71
|
+
|
|
72
|
+
| Method | Path | Body |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| POST | `/api/v1/darkube/apps/{id}/backup/download_information/` | `{backup_id:t}` |
|
|
75
|
+
| POST | `/api/v1/darkube/apps/{id}/backup/trigger_backup/` | `{backup_id:t}` |
|
|
76
|
+
| POST | `/api/v1/darkube/apps/{id}/backup/trigger_restore/` | `{snapshot_id:t}` |
|
|
77
|
+
| POST | `/api/v1/darkube/apps/{id}/compatible_plan/` | `{backupConfig:t}` |
|
|
78
|
+
| PUT | `/api/v1/darkube/database/user/{id}` | `{username:t,password:r}` |
|
|
79
|
+
| POST | `/api/v1/darkube/postgres_standby/check` | `{src_host:s,src_username:r,src_password:a,src_port:t,dst_tag:o}` |
|
|
80
|
+
| PUT | `/api/v1/darkube/promote-to-master/{id}` | `—` |
|
|
81
|
+
| POST | `/api/v1/darkube/pvcs/defrag/` | `e` |
|
|
82
|
+
| POST | `/api/v1/{paas}/apps/` | `a` |
|
|
83
|
+
| POST | `/api/v1/{paas}/apps/check_dns_records/` | `{hosts:a}` |
|
|
84
|
+
| POST | `/api/v1/{paas}/apps/check_subdomain/` | `{subdomain:a}` |
|
|
85
|
+
| POST | `/api/v1/{paas}/apps/docker_compose/` | `e` |
|
|
86
|
+
| POST | `/api/v1/{paas}/apps/schedule_info/` | `e` |
|
|
87
|
+
| DELETE | `/api/v1/{paas}/apps/{id}/` | `{git_repo_url:i}` |
|
|
88
|
+
| PATCH | `/api/v1/{paas}/apps/{id}/` | `{fields:["is_enabled"],is_enabled:i}` |
|
|
89
|
+
| PUT | `/api/v1/{paas}/apps/{id}/` | `i` |
|
|
90
|
+
| POST | `/api/v1/{paas}/apps/{id}/app_files/` | `a` |
|
|
91
|
+
| POST | `/api/v1/{paas}/apps/{id}/check_repo/` | `{git_repo_url:i}` |
|
|
92
|
+
| POST | `/api/v1/{paas}/apps/{id}/migrate/` | `i` |
|
|
93
|
+
| DELETE | `/api/v1/{paas}/apps/{id}/plugins/` | `—` |
|
|
94
|
+
| POST | `/api/v1/{paas}/apps/{id}/plugins/` | `{plugin_type:i}` |
|
|
95
|
+
| POST | `/api/v1/{paas}/apps/{id}/read_vault_secret/` | `{path:t}` |
|
|
96
|
+
| POST | `/api/v1/{paas}/apps/{id}/read_vault_secret_env/` | `{name:t}` |
|
|
97
|
+
| POST | `/api/v1/{paas}/apps/{id}/reinstall/` | `{}` |
|
|
98
|
+
| POST | `/api/v1/{paas}/apps/{id}/restart/` | `{}` |
|
|
99
|
+
| POST | `/api/v1/{paas}/apps/{id}/ssl_certificate_records/` | `{external_hosts:i}` |
|
|
100
|
+
| POST | `/api/v1/{paas}/apps/{id}/suggestions/` | `{}` |
|
|
101
|
+
| POST | `/api/v1/{paas}/apps/{id}/upgrade_version/` | `t` |
|
|
102
|
+
| POST | `/api/v1/{paas}/build/{id}/stop/` | `{}` |
|
|
103
|
+
| POST | `/api/v1/{paas}/deploy_contexts/` | `e` |
|
|
104
|
+
| DELETE | `/api/v1/{paas}/deploy_contexts/{id}/` | `a` |
|
|
105
|
+
| PUT | `/api/v1/{paas}/deploy_contexts/{id}/` | `i` |
|
|
106
|
+
| POST | `/api/v1/{paas}/github/branches/` | `{full_name:a}` |
|
|
107
|
+
| POST | `/api/v1/{paas}/gitlab/branches/` | `{id:a}` |
|
|
108
|
+
| POST | `/api/v1/{paas}/license/confluence/` | `a` |
|
|
109
|
+
| POST | `/api/v1/{paas}/license/jira/` | `a` |
|
|
110
|
+
| POST | `/api/v1/{paas}/license/jirasm/` | `a` |
|
|
111
|
+
| POST | `/api/v1/{paas}/license/plugin/` | `a` |
|
|
112
|
+
| POST | `/api/v1/{paas}/logs/loki/` | `{app_id:a,start:i,end:r,stream_name:s}` |
|
|
113
|
+
| POST | `/api/v1/{paas}/namespaces/` | `{name:a,cluster_id:i}` |
|
|
114
|
+
| PUT | `/api/v1/{paas}/permissions/app/{id}/` | `i` |
|
|
115
|
+
| PUT | `/api/v1/{paas}/permissions/user/{id}/` | `i` |
|
|
116
|
+
| POST | `/api/v1/{paas}/plans/calculator/` | `a` |
|
|
117
|
+
| POST | `/api/v1/{paas}/stateless_apps/sync_apps_data/` | `{}` |
|
|
118
|
+
| DELETE | `/api/v1/{paas}/template/` | `s` |
|
|
119
|
+
|
|
120
|
+
## Databases (DBaaS)
|
|
121
|
+
|
|
122
|
+
| Method | Path | Body |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| POST | `/dbaas/api/v1/app/backups/{app_id}/download_info/` | `—` |
|
|
125
|
+
| POST | `/dbaas/api/v1/app/backups/{app_id}/trigger_backup/` | `—` |
|
|
126
|
+
| POST | `/dbaas/api/v1/app/database/` | `{...a,cpu:(0,r.yr)(a.cpu),ram:(0,r.ft)(a.ram),disk:(0,r.ft)(a.disk)}` |
|
|
127
|
+
| POST | `/dbaas/api/v1/app/database/compatibility_check/` | `{src_host:e,src_username:l,src_password:o,src_port:i,dst_tag:r,engine:t}` |
|
|
128
|
+
| PUT | `/dbaas/api/v1/app/database/config/{app_id}/` | `{config:a.config}` |
|
|
129
|
+
| POST | `/dbaas/api/v1/app/database/readonly-cluster/` | `{...a,cpu:(0,r.yr)(a.cpu),ram:(0,r.ft)(a.ram),disk:(0,r.ft)(a.disk)}` |
|
|
130
|
+
| DELETE | `/dbaas/api/v1/app/database/{app_id}/` | `—` |
|
|
131
|
+
| PUT | `/dbaas/api/v1/app/database/{app_id}/` | `{plan:a.plan,disk:(0,r.ft)(a.disk),cpu:(0,r.yr)(a.cpu),ram:(0,r.ft)(a.ram),nodes:a.nodes}` |
|
|
132
|
+
| POST | `/dbaas/api/v1/app/database/{app_id}/external_access/` | `—` |
|
|
133
|
+
| POST | `/dbaas/api/v1/app/database/{app_id}/internal_access/` | `—` |
|
|
134
|
+
| POST | `/dbaas/api/v1/app/database/{app_id}/shutdown/` | `—` |
|
|
135
|
+
| POST | `/dbaas/api/v1/app/database/{app_id}/start/` | `—` |
|
|
136
|
+
| POST | `/dbaas/api/v1/app/database/{id}/promote/` | `—` |
|
|
137
|
+
| POST | `/dbaas/api/v1/app/db/database/{app_id}/` | `{db_name:a.database}` |
|
|
138
|
+
| DELETE | `/dbaas/api/v1/app/db/database/{app_id}/{id}/` | `{db_name:a.database}` |
|
|
139
|
+
| PUT | `/dbaas/api/v1/app/permissions/database/{app_id}/` | `a.permissions` |
|
|
140
|
+
| DELETE | `/dbaas/api/v1/app/pghero/{app_id}` | `—` |
|
|
141
|
+
| POST | `/dbaas/api/v1/app/pghero/{app_id}` | `—` |
|
|
142
|
+
| POST | `/dbaas/api/v1/app/pools/database/{app_id}/` | `{pool_name:a.poolName,pool_mode:a.poolMode,db_name:a.database,user_name:a.username,pool_size:a.poolSize}` |
|
|
143
|
+
| DELETE | `/dbaas/api/v1/app/pools/database/{app_id}/{id}/` | `{pool_name:a.poolName,pool_mode:a.poolMode,db_name:a.database,user_name:a.username,pool_size:a.poolSize}` |
|
|
144
|
+
| PUT | `/dbaas/api/v1/app/pools/database/{app_id}/{id}/` | `{pool_mode:a.poolMode,db_name:a.database,user_name:a.username,pool_size:a.poolSize}` |
|
|
145
|
+
| POST | `/dbaas/api/v1/app/query-analysis/{app_id}/activate/` | `i` |
|
|
146
|
+
| POST | `/dbaas/api/v1/app/query-analysis/{id}/metrics/` | `i` |
|
|
147
|
+
| POST | `/dbaas/api/v1/app/query-analysis/{id}/report/` | `i` |
|
|
148
|
+
| POST | `/dbaas/api/v1/app/user/database/{app_id}/` | `{username:a.username}` |
|
|
149
|
+
| DELETE | `/dbaas/api/v1/app/user/database/{app_id}/{id}/` | `—` |
|
|
150
|
+
| PUT | `/dbaas/api/v1/app/user/database/{app_id}/{id}/change_password/` | `{username:a.username}` |
|
|
151
|
+
|
|
152
|
+
## Container Registry
|
|
153
|
+
|
|
154
|
+
| Method | Path | Body |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| POST | `/api/v1/registry-gc-strategies/` | `—` |
|
|
157
|
+
| DELETE | `/api/v1/registry-gc-strategies/{id}/` | `—` |
|
|
158
|
+
| PUT | `/api/v1/registry-gc-strategies/{id}/` | `i` |
|
|
159
|
+
| POST | `/api/v1/registry/` | `a` |
|
|
160
|
+
| DELETE | `/api/v1/registry/{id}/` | `{secret_fields:["vault_password"]}` |
|
|
161
|
+
| POST | `/api/v1/registry/{id}/change_password/` | `i` |
|
|
162
|
+
| POST | `/api/v1/registry/{id}/delete_digests/` | `{repository_name:i,digests_sha:r}` |
|
|
163
|
+
| POST | `/api/v1/registry/{id}/delete_repository/` | `{repository_name:i}` |
|
|
164
|
+
| POST | `/api/v1/registry/{id}/delete_tags/` | `{repository_name:i,tags:r,digest_sha:s}` |
|
|
165
|
+
| POST | `/api/v1/registry/{id}/list_repository_digests/` | `{repository_name:i}` |
|
|
166
|
+
| POST | `/api/v1/registry/{id}/read_vault_secret/` | `{secret_fields:["vault_password"]}` |
|
|
167
|
+
|
|
168
|
+
## Monitoring & Logging
|
|
169
|
+
|
|
170
|
+
| Method | Path | Body |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| POST | `/api/v1/datasources/` | `{name:s,plan:a,datasource_type:r,zone_id:i}` |
|
|
173
|
+
| DELETE | `/api/v1/datasources/zones/` | `—` |
|
|
174
|
+
| DELETE | `/api/v1/datasources/{id}` | `{username:s,role:a}` |
|
|
175
|
+
| POST | `/api/v1/datasources/{id}/users/` | `{username:s,role:a}` |
|
|
176
|
+
| DELETE | `/api/v1/datasources/{id}/users/{id}/` | `—` |
|
|
177
|
+
| POST | `/api/v1/dazzle/metric-datasources/` | `{}` |
|
|
178
|
+
| DELETE | `/api/v1/dazzle/metric-datasources/{id}/` | `{secret_fields:["vault_password"]}` |
|
|
179
|
+
| POST | `/api/v1/dazzle/metric-datasources/{id}/read_vault_secret/` | `{secret_fields:["vault_password"]}` |
|
|
180
|
+
| POST | `/api/v1/hamartia/datasources/` | `{}` |
|
|
181
|
+
| DELETE | `/api/v1/hamartia/datasources/{id}/` | `i` |
|
|
182
|
+
| POST | `/api/v1/hamartia/datasources/{id}/pipelines/` | `i` |
|
|
183
|
+
| DELETE | `/api/v1/hamartia/datasources/{id}/pipelines/?pipeline_id={id}` | `{secret_fields:["vault_password"]}` |
|
|
184
|
+
| POST | `/api/v1/hamartia/datasources/{id}/read_vault_secret/` | `{secret_fields:["vault_password"]}` |
|
|
185
|
+
| POST | `/api/v1/logstorage/loki/` | `{name:s,controller_loki_service:a,users:l}` |
|
|
186
|
+
| DELETE | `/api/v1/logstorage/loki/{id}/` | `—` |
|
|
187
|
+
| PUT | `/api/v1/logstorage/loki/{id}/` | `{users:l}` |
|
|
188
|
+
| DELETE | `/api/v1/logstorage/loki/{id}/users/{id}/` | `{data_store_name:a,` |
|
|
189
|
+
| POST | `/api/v1/loki/usages/` | `{data_store_name:a,data_store_id:t}` |
|
|
190
|
+
|
|
191
|
+
## Marketplace / SaaS
|
|
192
|
+
|
|
193
|
+
| Method | Path | Body |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| POST | `/api/v1/app/license/{id}/` | `o` |
|
|
196
|
+
| POST | `/api/v1/app/saas/` | `e` |
|
|
197
|
+
| POST | `/api/v1/app/saas/set_ssl_challenge_dns01/` | `{app_id:e.serviceId,domain:e.domain}` |
|
|
198
|
+
| DELETE | `/api/v1/app/saas/{id}/` | `e` |
|
|
199
|
+
| PUT | `/api/v1/app/saas/{id}/` | `o` |
|
|
200
|
+
| POST | `/api/v1/app/saas/{id}/backup/download_info/` | `{backup_id:o}` |
|
|
201
|
+
| POST | `/api/v1/app/saas/{id}/backup/trigger_backup/` | `{backup_id:o}` |
|
|
202
|
+
| POST | `/api/v1/app/saas/{id}/backup/trigger_restore/` | `{snapshot_id:o}` |
|
|
203
|
+
| POST | `/api/v1/app/saas/{id}/check_upgrade_compatibility/` | `o` |
|
|
204
|
+
| POST | `/api/v1/app/saas/{id}/disable_filebrowser/` | `—` |
|
|
205
|
+
| POST | `/api/v1/app/saas/{id}/enable_filebrowser/` | `o` |
|
|
206
|
+
| POST | `/api/v1/app/saas/{id}/restart/` | `o` |
|
|
207
|
+
| POST | `/api/v1/app/saas/{id}/set_ssl_challenge_type/` | `{ssl_challenge_type:e.ssl_challenge_type}` |
|
|
208
|
+
| POST | `/api/v1/app/saas/{id}/shown_field/` | `o` |
|
|
209
|
+
| POST | `/api/v1/app/saas/{id}/ssl_certificate_records/` | `{external_hosts:e.external_hosts}` |
|
|
210
|
+
| PUT | `/api/v1/app/saas/{id}/version_upgrade/` | `o` |
|
|
211
|
+
|
|
212
|
+
## Organizations & Members
|
|
213
|
+
|
|
214
|
+
| Method | Path | Body |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| POST | `/api/v1/organizations/join_by_token` | `{token:a,organization_id:t}` |
|
|
217
|
+
| POST | `/api/v1/organizations/{id}/accept-transfer` | `null` |
|
|
218
|
+
| POST | `/api/v1/organizations/{id}/cancel-transfer` | `null` |
|
|
219
|
+
| PUT | `/api/v1/organizations/{id}/edit` | `i` |
|
|
220
|
+
| DELETE | `/api/v1/organizations/{id}/enforce-2fa/` | `e` |
|
|
221
|
+
| POST | `/api/v1/organizations/{id}/enforce-2fa/` | `—` |
|
|
222
|
+
| POST | `/api/v1/organizations/{id}/initiate-transfer` | `{target_user_email:i}` |
|
|
223
|
+
| POST | `/api/v1/organizations/{id}/leave` | `null` |
|
|
224
|
+
| PUT | `/api/v1/organizations/{id}/members` | `i` |
|
|
225
|
+
| POST | `/api/v1/organizations/{id}/reject-transfer` | `null` |
|
|
226
|
+
|
|
227
|
+
## Billing
|
|
228
|
+
|
|
229
|
+
| Method | Path | Body |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| POST | `/api/v1/billing/billbillak/history/` | `{app_name:t,cluster:i,namespace:a,resource:r,owner_kind:n,start_time:l,end_time:s}` |
|
|
232
|
+
| POST | `/api/v1/billing/invoices/export_report/` | `{start_date:t,end_date:i}` |
|
|
233
|
+
| POST | `/api/v1/billing/invoices/invoice_settings/` | `e` |
|
|
234
|
+
|
|
235
|
+
## Sentry
|
|
236
|
+
|
|
237
|
+
| Method | Path | Body |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| POST | `/api/v1/sentry/sentrys/` | `e` |
|
|
240
|
+
| DELETE | `/api/v1/sentry/sentrys/{id}/` | `t` |
|
|
241
|
+
| PATCH | `/api/v1/sentry/sentrys/{id}/` | `t` |
|
|
242
|
+
|
|
243
|
+
## Notifications & Alerts
|
|
244
|
+
|
|
245
|
+
| Method | Path | Body |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| POST | `/api/v1/viper/alert_routes/` | `a` |
|
|
248
|
+
| DELETE | `/api/v1/viper/alert_routes/{id}/` | `—` |
|
|
249
|
+
| POST | `/notifications/alerts/api/organization-id-alert-routers/` | `e` |
|
|
250
|
+
|
|
251
|
+
## Backup
|
|
252
|
+
|
|
253
|
+
| Method | Path | Body |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| POST | `/api/v1/backups/storage_destinations/{id}/read_vault_secret/` | `{secret_fields:["vault_password"]}` |
|
|
256
|
+
|
|
257
|
+
## Other
|
|
258
|
+
|
|
259
|
+
| Method | Path | Body |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| PUT | `/api/v1/{id}/permissions/user/{id}/` | `o` |
|
|
262
|
+
| POST | `/backup/` | `{...s,worker_type:"rclone",sync_mode:"backup"}` |
|
|
263
|
+
| DELETE | `/backup/{id}/` | `{user_email:e.user_email,st` |
|
|
264
|
+
| DELETE | `/backup/{id}/progress` | `{user_email:e.user_email,storages_access:e.storages_access}` |
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hamravesh-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the Hamravesh / Darkube console API — manage apps, deploys, databases, registries and billing from any MCP client (Claude, etc.).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"hamravesh-mcp": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
".env.example",
|
|
16
|
+
"ENDPOINTS.md",
|
|
17
|
+
"WRITE-ENDPOINTS.md",
|
|
18
|
+
"LIVE-STATUS.md",
|
|
19
|
+
"ENDPOINTS-RAW.txt"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node src/index.js",
|
|
23
|
+
"test": "node test/ci.js",
|
|
24
|
+
"smoke": "node test/smoke.js",
|
|
25
|
+
"smoke:write": "node test/write-smoke.js"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"modelcontextprotocol",
|
|
34
|
+
"hamravesh",
|
|
35
|
+
"darkube",
|
|
36
|
+
"kubernetes",
|
|
37
|
+
"paas",
|
|
38
|
+
"devops",
|
|
39
|
+
"claude",
|
|
40
|
+
"ai"
|
|
41
|
+
],
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"author": "Mohammad Bakhtari <skyborn91@gmail.com>",
|
|
44
|
+
"homepage": "https://github.com/bakhtarimohammad/hamravesh-mcp#readme",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/bakhtarimohammad/hamravesh-mcp.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/bakhtarimohammad/hamravesh-mcp/issues"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.18.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* client.js — کلاینت HTTP برای API داخلی کنسول همروش (api.hamravesh.com).
|
|
4
|
+
*
|
|
5
|
+
* دو روش احراز هویت:
|
|
6
|
+
* 1) API Key (توصیهشده): هدر Authorization: Api-Key <key>
|
|
7
|
+
* 2) ایمیل/رمز: لاگین JWT + تمدید خودکار توکن
|
|
8
|
+
*
|
|
9
|
+
* این کلاینت از fetch داخلی Node (>=18) استفاده میکند؛ هیچ وابستگی شبکهای ندارد.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const DEFAULT_BASE = "https://api.hamravesh.com";
|
|
13
|
+
|
|
14
|
+
/** @param {string} token */
|
|
15
|
+
function jwtExp(token) {
|
|
16
|
+
try {
|
|
17
|
+
const payload = token.split(".")[1];
|
|
18
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
19
|
+
return (JSON.parse(json).exp || 0) * 1000; // ms
|
|
20
|
+
} catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class HamraveshError extends Error {
|
|
26
|
+
/** @param {number} status @param {any} body */
|
|
27
|
+
constructor(status, body) {
|
|
28
|
+
const msg = typeof body === "string" ? body : JSON.stringify(body);
|
|
29
|
+
super(`HTTP ${status}: ${msg}`);
|
|
30
|
+
this.name = "HamraveshError";
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.body = body;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class HamraveshClient {
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} cfg
|
|
39
|
+
* @param {string} [cfg.apiKey]
|
|
40
|
+
* @param {string} [cfg.email]
|
|
41
|
+
* @param {string} [cfg.password]
|
|
42
|
+
* @param {string} [cfg.otp]
|
|
43
|
+
* @param {string} [cfg.org] سازمان پیشفرض (X-Organization)
|
|
44
|
+
* @param {string} [cfg.base] base URL
|
|
45
|
+
*/
|
|
46
|
+
constructor(cfg = {}) {
|
|
47
|
+
this.apiKey = cfg.apiKey || "";
|
|
48
|
+
this.email = cfg.email || "";
|
|
49
|
+
this.password = cfg.password || "";
|
|
50
|
+
this.otp = cfg.otp || "";
|
|
51
|
+
this.org = cfg.org || "";
|
|
52
|
+
this.base = (cfg.base || DEFAULT_BASE).replace(/\/$/, "");
|
|
53
|
+
/** @type {{access?:string, refresh?:string}} */
|
|
54
|
+
this.tokens = {};
|
|
55
|
+
if (!this.apiKey && (!this.email || !this.password)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"احراز هویت تنظیم نشده: یا HAMRAVESH_API_KEY بده، یا HAMRAVESH_EMAIL + HAMRAVESH_PASSWORD."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get authMode() {
|
|
63
|
+
return this.apiKey ? "api-key" : "password";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async _login() {
|
|
67
|
+
const r = await fetch(this.base + "/api/v1/token/", {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
email: this.email,
|
|
72
|
+
password: this.password,
|
|
73
|
+
captcha: "",
|
|
74
|
+
client_time: Date.now(),
|
|
75
|
+
...(this.otp ? { otp: this.otp } : {}),
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
// متن را اول بگیر تا اگر JSON نبود، خطای واقعی گم نشود
|
|
79
|
+
const text = await r.text();
|
|
80
|
+
let body;
|
|
81
|
+
try {
|
|
82
|
+
body = text ? JSON.parse(text) : {};
|
|
83
|
+
} catch {
|
|
84
|
+
body = text;
|
|
85
|
+
}
|
|
86
|
+
if (!r.ok) throw new HamraveshError(r.status, body);
|
|
87
|
+
this.tokens = { access: body.access, refresh: body.refresh };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async _refresh() {
|
|
91
|
+
const rt = this.tokens.refresh;
|
|
92
|
+
if (!rt || jwtExp(rt) < Date.now() + 30_000) return this._login();
|
|
93
|
+
const r = await fetch(this.base + "/api/v1/token/refresh/", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
96
|
+
body: JSON.stringify({ refresh: rt }),
|
|
97
|
+
});
|
|
98
|
+
if (!r.ok) return this._login();
|
|
99
|
+
const body = await r.json().catch(() => ({}));
|
|
100
|
+
this.tokens.access = body.access;
|
|
101
|
+
if (body.refresh) this.tokens.refresh = body.refresh;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async _authHeader() {
|
|
105
|
+
if (this.apiKey) return "Api-Key " + this.apiKey;
|
|
106
|
+
const exp = this.tokens.access ? jwtExp(this.tokens.access) : 0;
|
|
107
|
+
if (!this.tokens.access || exp <= 0 || exp < Date.now() + 60_000) {
|
|
108
|
+
await this._refresh();
|
|
109
|
+
}
|
|
110
|
+
return "Bearer " + this.tokens.access;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* یک درخواست خام به API همروش.
|
|
115
|
+
* @param {string} method
|
|
116
|
+
* @param {string} path مثل /api/v1/darkube/apps/ یا URL کامل
|
|
117
|
+
* @param {object} [opts]
|
|
118
|
+
* @param {string} [opts.org]
|
|
119
|
+
* @param {Record<string,any>} [opts.params]
|
|
120
|
+
* @param {any} [opts.body]
|
|
121
|
+
* @returns {Promise<{status:number, body:any}>}
|
|
122
|
+
*/
|
|
123
|
+
async request(method, path, opts = {}) {
|
|
124
|
+
const org = opts.org || this.org;
|
|
125
|
+
// قفلِ میزبان: فقط با base host خودِ همروش حرف بزن (جلوگیری از SSRF)
|
|
126
|
+
let url;
|
|
127
|
+
if (path.startsWith("http")) {
|
|
128
|
+
const u = new URL(path);
|
|
129
|
+
const baseHost = new URL(this.base).host;
|
|
130
|
+
if (u.host !== baseHost) {
|
|
131
|
+
throw new Error(`میزبان غیرمجاز: ${u.host} — فقط ${baseHost} مجاز است.`);
|
|
132
|
+
}
|
|
133
|
+
url = path;
|
|
134
|
+
} else {
|
|
135
|
+
if (!path.startsWith("/")) throw new Error("مسیر باید با / شروع شود.");
|
|
136
|
+
url = this.base + path;
|
|
137
|
+
}
|
|
138
|
+
if (opts.params && typeof opts.params === "object" && !Array.isArray(opts.params) && Object.keys(opts.params).length) {
|
|
139
|
+
const qs = new URLSearchParams();
|
|
140
|
+
for (const [k, v] of Object.entries(opts.params)) {
|
|
141
|
+
if (v === undefined || v === null) continue;
|
|
142
|
+
if (Array.isArray(v)) v.forEach((x) => qs.append(k, String(x)));
|
|
143
|
+
else qs.append(k, String(v));
|
|
144
|
+
}
|
|
145
|
+
url += (url.includes("?") ? "&" : "?") + qs.toString();
|
|
146
|
+
}
|
|
147
|
+
const doFetch = async () => {
|
|
148
|
+
const headers = {
|
|
149
|
+
accept: "application/json, text/plain, */*",
|
|
150
|
+
"accept-language": "fa",
|
|
151
|
+
authorization: await this._authHeader(),
|
|
152
|
+
};
|
|
153
|
+
if (org) headers["x-organization"] = org;
|
|
154
|
+
/** @type {RequestInit} */
|
|
155
|
+
const init = { method: method.toUpperCase(), headers };
|
|
156
|
+
if (opts.body !== undefined && method.toUpperCase() !== "GET") {
|
|
157
|
+
headers["content-type"] = "application/json";
|
|
158
|
+
init.body = JSON.stringify(opts.body);
|
|
159
|
+
}
|
|
160
|
+
return fetch(url, init);
|
|
161
|
+
};
|
|
162
|
+
let r = await doFetch();
|
|
163
|
+
if (r.status === 401 && this.authMode === "password") {
|
|
164
|
+
await this._login();
|
|
165
|
+
r = await doFetch();
|
|
166
|
+
}
|
|
167
|
+
let body;
|
|
168
|
+
const text = await r.text();
|
|
169
|
+
try {
|
|
170
|
+
body = text ? JSON.parse(text) : "";
|
|
171
|
+
} catch {
|
|
172
|
+
body = text;
|
|
173
|
+
}
|
|
174
|
+
if (!r.ok) throw new HamraveshError(r.status, body);
|
|
175
|
+
return { status: r.status, body };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get(path, opts) {
|
|
179
|
+
return this.request("GET", path, opts);
|
|
180
|
+
}
|
|
181
|
+
}
|