systa-mcp 1.0.0 → 1.0.8
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 +47 -7
- package/SKILL.md +93 -14
- package/package.json +1 -1
- package/server.js +247 -34
package/README.md
CHANGED
|
@@ -9,7 +9,47 @@ ile SysTa REST API'sini "araç" olarak kullanmasını sağlar.
|
|
|
9
9
|
`Authorization: Bearer <key>` + `X-Vendor-Code` ile isteği proxy'ler; anahtarın kapsamı
|
|
10
10
|
dışındaki çağrılar backend tarafından `403` ile reddedilir.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Hızlı Başlangıç (AI İstemci)
|
|
13
|
+
|
|
14
|
+
Harici bir AI istemciyi (Claude Code, MCP uyumlu ajan vb.) SysTa'ya bağlamak için
|
|
15
|
+
aşağıdaki adımları izleyin.
|
|
16
|
+
|
|
17
|
+
### 1. API Anahtarı Oluşturun
|
|
18
|
+
|
|
19
|
+
SysTa'da **Ayarlar > API Anahtarları** sayfasından scoped API key oluşturun.
|
|
20
|
+
Anahtar yalnızca oluşturma anında gösterilir — güvenli bir yerde saklayın.
|
|
21
|
+
|
|
22
|
+
### 2. Claude Code'a MCP Sunucusunu Ekleyin
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add systa \
|
|
26
|
+
-e SYSTA_API_KEY=sk_live_ANAHTARINIZ \
|
|
27
|
+
-e SYSTA_API_BASE_URL=https://systa.vizyoneks.com.tr/api \
|
|
28
|
+
-- npx -y systa-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 3. Skill'i Yükleyin
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx -y systa-mcp --install-skill
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 4. Doğrulama
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Kimlik kontrolü
|
|
41
|
+
curl -H "Authorization: Bearer sk_live_..." https://systa.vizyoneks.com.tr/api/auth/me
|
|
42
|
+
|
|
43
|
+
# Yetenek kataloğu
|
|
44
|
+
curl -H "Authorization: Bearer sk_live_..." https://systa.vizyoneks.com.tr/api/api-keys/me/capabilities
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Her iki endpoint de `200` dönüyorsa bağlantı hazırdır. `.env.example` dosyasını
|
|
48
|
+
referans olarak kullanabilirsiniz (`cp .env.example .env` ile kopyalayıp doldurun).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Kurulum (Detaylı)
|
|
13
53
|
|
|
14
54
|
Anahtarı SysTa'da **Ayarlar → API Anahtarları → Yeni Anahtar** ile üretin (scope'ları
|
|
15
55
|
seçin; anahtar yalnızca o anda gösterilir). Anahtar **self-routing**'tir: vendor bilgisi
|
|
@@ -34,12 +74,12 @@ npx -y systa-mcp --install-skill # -> ~/.claude/skills/systa/SKILL.md
|
|
|
34
74
|
|
|
35
75
|
### Çevre değişkenleri
|
|
36
76
|
|
|
37
|
-
| Değişken | Örnek | Açıklama
|
|
38
|
-
| ------------------------ | ------------------------------- |
|
|
39
|
-
| `SYSTA_API_BASE_URL` | `https://systa.example.com/api` | API kök adresi (`/api` dahil), nötr host
|
|
40
|
-
| `SYSTA_API_KEY` | `sk_live_<vendor>_...` | Profilden üretilen anahtar (bir kez gösterilir)
|
|
41
|
-
| `SYSTA_VENDOR_CODE` | `vizyoneks` | OPSIYONEL — verilirse anahtarın vendor'ıyla aynı olmalı; boş bırakılırsa backend anahtardan türetir
|
|
42
|
-
| `SYSTA_MCP_STRICT_TOOLS` | `1` | OPSIYONEL — kapsam çözülemezse tüm aksiyon araçlarını gizle
|
|
77
|
+
| Değişken | Örnek | Açıklama |
|
|
78
|
+
| ------------------------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `SYSTA_API_BASE_URL` | `https://systa.example.com/api` | API kök adresi (`/api` dahil), nötr host |
|
|
80
|
+
| `SYSTA_API_KEY` | `sk_live_<vendor>_...` | Profilden üretilen anahtar (bir kez gösterilir) |
|
|
81
|
+
| `SYSTA_VENDOR_CODE` | `vizyoneks` | OPSIYONEL — verilirse anahtarın vendor'ıyla aynı olmalı; boş bırakılırsa backend anahtardan türetir |
|
|
82
|
+
| `SYSTA_MCP_STRICT_TOOLS` | `1` | OPSIYONEL — kapsam çözülemezse tüm aksiyon araçlarını gizle. Değer TAM olarak `1` olmalı; aksi halde fail-open (kapsam çözülemese de araçlar gösterilir) |
|
|
43
83
|
|
|
44
84
|
### Yerel geliştirme / yerel klona karşı
|
|
45
85
|
|
package/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version:
|
|
2
|
+
version: 1.0.8
|
|
3
3
|
name: systa
|
|
4
4
|
description: |
|
|
5
5
|
Drive SysTa (Talep Takip Sistemi / SysTa) — a
|
|
@@ -48,7 +48,7 @@ Before any SysTa action, confirm connectivity, auth and identity. Do this once p
|
|
|
48
48
|
If there is no MCP server, fall back to the REST API with a scoped API key. The key is **never typed into chat** — read it from the environment / a local secret file:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
: "${
|
|
51
|
+
: "${SYSTA_API_BASE_URL:?set SYSTA_API_BASE_URL, e.g. https://systa.example.com/api}"
|
|
52
52
|
: "${SYSTA_API_KEY:?export a scoped sk_live_... key (do not paste in chat)}"
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ Before any SysTa action, confirm connectivity, auth and identity. Do this once p
|
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
58
|
curl -fsS -H "Authorization: Bearer $SYSTA_API_KEY" \
|
|
59
|
-
"$
|
|
59
|
+
"$SYSTA_API_BASE_URL/auth/me"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Capture: `userId`, `companyId` (default company), `vendorSchema`, and the permission/scope set. If this returns `401`/`403` or `API key not found`, stop and tell the user the key is missing/expired/over-scoped — do **not** guess credentials.
|
|
@@ -87,13 +87,55 @@ Short shared vocabulary. Use these words with the user; never expose raw REST pa
|
|
|
87
87
|
| Sürüm | Release | Yazılım sürüm yönetimi: liste → item → onay → Go/No-Go → deployment workflow. |
|
|
88
88
|
| Vendor şema | Vendor schema | Multi-tenant izolasyon; her vendor ayrı schema (`vendor_xxx`). Public referans alınmaz. |
|
|
89
89
|
|
|
90
|
+
### Anahtar kelime → modül eşlemesi (keyword index)
|
|
91
|
+
|
|
92
|
+
Kullanıcının doğal-dil kelimesini doğru modüle hızlı eşle. Bu, MCP `list_capabilities`
|
|
93
|
+
yanıtındaki `glossary` alias'larının ayna kopyasıdır (MCP yokken de geçerli). Alias'lar
|
|
94
|
+
ASCII'dir — eşleştirmede aksanı yok say. Modülü bulduktan sonra `describe_module(modül)`
|
|
95
|
+
veya `describe_endpoint` ile gövdeyi netleştir.
|
|
96
|
+
|
|
97
|
+
| Doğal dil (alias) | Modül | Birincil giriş |
|
|
98
|
+
| --------------------------------------------------------- | --------------- | ----------------------------------------------- |
|
|
99
|
+
| talep, talepler, is, destek talebi | request | list_requests / create_request |
|
|
100
|
+
| gorev, task, kart, card | kanban | manage_request_card / move_request_card |
|
|
101
|
+
| pano, board, kanban / kolon, column, asama, liste | kanban | get_request_board |
|
|
102
|
+
| efor, durum, oncelik, atanan, planla, denetim, gecmis | request | get_request / list_requests |
|
|
103
|
+
| proje, klonla | project | list_projects / create_project |
|
|
104
|
+
| onaya sunma, snapshot, sprint, sprinti kapat, optimize et | plan | submit_plan_for_approval / create_plan_baseline |
|
|
105
|
+
| onay, onayla, reddet | approvals | list_pending_approvals / respond_to_approval |
|
|
106
|
+
| sla, sure taahhudu | sla | (sla endpoints) |
|
|
107
|
+
| release, surum, go/no-go, deployment | release | create_release_list / add_release_item |
|
|
108
|
+
| musaitlik, izin (leave), vekil | user/automation | systa_api_call /user-availability, /user-leaves |
|
|
109
|
+
| vendor, sirket, musteri | company | systa_api_call GET /companies |
|
|
110
|
+
| organizasyon birimi, departman, bolum, birim | department | GET /api/departments |
|
|
111
|
+
| takim, ekip, team | team | GET /api/teams |
|
|
112
|
+
| yetki paketi, izin (permission) | security | (bundle/permission endpoints) |
|
|
113
|
+
| alt form, sub-form | forms | systa_api_call GET /forms/:id |
|
|
114
|
+
| rapor, raporlama, disa aktar, export, excel, csv | report | systa_api_call /reports/preview,/reports/export |
|
|
115
|
+
| ozel sorgu, sorgu, query builder, ad-hoc, veri cek | query-builder | querybuilder_describe_fields / \_execute_query |
|
|
116
|
+
| hesap tablosu, gridpad, tablo, excel, spreadsheet, hucre | spreadsheet | GET /api/spreadsheets |
|
|
117
|
+
| dosya, ek, attachment, yukle, upload, indir, download | file | upload_file_to / download_request_file |
|
|
118
|
+
| bildirim, notification, uyari, okundu, push | notification | GET /api/notifications |
|
|
119
|
+
| yetenek, beceri, skill, otomatik etiketleme | skill | GET /api/skills |
|
|
120
|
+
| duyuru, announcement, bildiri, ilan | announcement | GET /api/announcements |
|
|
121
|
+
| urun guncellemesi, surum notu, yenilikler, changelog | product-update | GET /api/product-updates |
|
|
122
|
+
| not, kisisel not, hatirlatma | note | GET /api/dashboard/notes |
|
|
123
|
+
| yer imi, favori, bookmark, kisayol | bookmark | GET /api/dashboard/bookmarks |
|
|
124
|
+
| degerlendirme, puanlama, anket, memnuniyet, geri bildirim | ratings | POST /api/ratings |
|
|
125
|
+
| github, repo, pull request, commit, entegrasyon | github | GET /api/github/connection |
|
|
126
|
+
| gosterge paneli, dashboard, widget, ozet, grafik, panel | dashboard | GET /api/dashboard/init |
|
|
127
|
+
|
|
128
|
+
> Bu tablo, katalogdaki ~25 modülün kullanıcı-yüzeyli olanlarından bir alt küme kapsar. Listede olmayan
|
|
129
|
+
> bir kelime için `list_capabilities` çağır — anahtarının erişebildiği güncel modül/alias
|
|
130
|
+
> kümesini döndürür (kapsam-filtreli; yetkisiz modül görünmez).
|
|
131
|
+
|
|
90
132
|
## UX Rules
|
|
91
133
|
|
|
92
134
|
1. **Be concise; summarize.** No raw IDs, no JSON dumps in chat. Report "Talep #1423 açıldı — 'Login hatası', sana atandı" / "Request #1423 created and assigned to you." Surface a stable handle the user recognizes (talep numarası, başlık), not internal uuids.
|
|
93
135
|
2. **No jargon.** Don't narrate "calling describe_endpoint", "POST /api/requests", "resolving statusId". Speak in the product's user verbs.
|
|
94
136
|
3. **Detect language and reply in it.** If the user writes Turkish, answer in Turkish; if English, English. Field keys and enum values passed to the API stay as the API expects (`statusKey`, `priority=high`) — only the conversation is localized.
|
|
95
137
|
4. **Smart defaults + ask one thing at a time.** Apply opinionated defaults from the intent summary (`channel='API'`, `priority='normal'`, assignee=self for "bana", `strategy='any_one'`/`'all_required'` per module). Ask **one** genuinely-missing thing, never a batch ("ver bana başlık, şirket, durum, öncelik, kategori" YASAK). Never invent a `title`, a password, a webhook URL, or a secret — ask for those.
|
|
96
|
-
5. **Resolve dynamic required fields up front.** Before a create/mutation, resolve the entity's required fields _for this vendor_ (e.g. `statusId` via `GET /api/statuses`, `companyId` from `who-am-i`, approver name → `userId` via user lookup). Don't submit a half-filled body and let it 400 — pre-resolve, then submit. Forms with dynamic schemas: pull the schema first (`
|
|
138
|
+
5. **Resolve dynamic required fields up front.** Before a create/mutation, resolve the entity's required fields _for this vendor_ (e.g. `statusId` via `GET /api/statuses`, `companyId` from `who-am-i`, approver name → `userId` via user lookup). Don't submit a half-filled body and let it 400 — pre-resolve, then submit. Forms with dynamic schemas: pull the schema first (via `systa_api_call`, e.g. `GET /forms/:id`) so required fields are known.
|
|
97
139
|
6. **Safety tiers gate confirmation.** `read_only` → just do it. `mutating`/`idempotent` → do it, then report. **`destructive`** (revoke access, delete, hard removal) and live side-effecting actions (publish, send invite, advance release workflow, test webhook) → show a one-line "şunu yapacağım" and get an explicit OK first. Publishing (announcement/release notes/product update) is **never** auto-published — show a summary, get approval.
|
|
98
140
|
7. **Don't over-ask scope.** `companyId`/`userId`/`vendorSchema` come from `who-am-i`; don't ask the user for them when a single sane value exists.
|
|
99
141
|
|
|
@@ -143,13 +185,13 @@ Each maps a P0 user verb to a tool/endpoint, with the opinionated defaults from
|
|
|
143
185
|
|
|
144
186
|
- List: `GET /api/projects` (default `page=1, limit=20`, recent first). "durum/sağlık" → combine `health-score` + `risk-score` + `sla-status` into one summary.
|
|
145
187
|
|
|
146
|
-
### Kanban kart ekle (add a task card) — `
|
|
188
|
+
### Kanban kart ekle (add a task card) — `manage_request_card` (create)
|
|
147
189
|
|
|
148
|
-
- `boardId` is always tied to a `requestId` — first resolve the board
|
|
190
|
+
- `boardId` is always tied to a `requestId` — first resolve the board (`get_request_board`, or `GET /api/requests/:requestId/kanban/hierarchy`). Defaults: first/entry column if `columnId` omitted, `priority='normal'`, unassigned unless "bana ata", `cardTitle` min 3 chars (ask to clarify if shorter). `manage_request_card` handles create / update / complete (the action is in the body) — call `describe_endpoint` if unsure of the fields.
|
|
149
191
|
|
|
150
|
-
### Kartı taşı / tamamla (move / complete card) — `
|
|
192
|
+
### Kartı taşı / tamamla (move / complete card) — `move_request_card` / `manage_request_card`
|
|
151
193
|
|
|
152
|
-
- Move: `
|
|
194
|
+
- Move: `move_request_card` — resolve the target column name → `columnId` from the board detail; append to end if no position given. Complete: `manage_request_card` with the complete action — server sets `isCompleted=true`, 100%.
|
|
153
195
|
|
|
154
196
|
### Onaya sun / onayla / bekleyenler (approvals)
|
|
155
197
|
|
|
@@ -157,13 +199,13 @@ Each maps a P0 user verb to a tool/endpoint, with the opinionated defaults from
|
|
|
157
199
|
- Respond: `respond_to_approval` → `POST /api/approvals/responses/:id/respond`; "onayla"→`approved`, "reddet"→`rejected` (rette kısa sebep iste, zorunlu değil).
|
|
158
200
|
- Pending: `list_pending_approvals` → `GET /api/approvals/requests-awaiting` (mevcut kullanıcının onayını bekleyenler).
|
|
159
201
|
|
|
160
|
-
### İzin talebi (leave) — `
|
|
202
|
+
### İzin talebi (leave) — `systa_api_call` `POST /user-leaves` (no named tool)
|
|
161
203
|
|
|
162
|
-
- `userId`=self (sormadan), `leaveType='annual'`, `isHalfDay=false`, dates normalized to `YYYY-MM-DD` ("yarın/cuma" → tarih), `returnToWorkDate` = next working day after end. "izinlerim" → `
|
|
204
|
+
- No dedicated tool — drive via `systa_api_call`. `userId`=self (sormadan), `leaveType='annual'`, `isHalfDay=false`, dates normalized to `YYYY-MM-DD` ("yarın/cuma" → tarih), `returnToWorkDate` = next working day after end. "izinlerim" → `systa_api_call` `GET /user-leaves/me`. Needs the `user.leave.self` scope.
|
|
163
205
|
|
|
164
|
-
### Rapor al / dışa aktar (report) — `
|
|
206
|
+
### Rapor al / dışa aktar (report) — `systa_api_call` (no named tool)
|
|
165
207
|
|
|
166
|
-
- Önce önizle (`POST /
|
|
208
|
+
- No dedicated tool — drive via `systa_api_call`. Önce önizle (`POST /reports/preview`, read-only), sonra dışa aktar (`POST /reports/export`, `format='xlsx'` unless "csv"). `reportType` is the required pivot (request/sla/user/organization/project/lifecycle/subform) — infer from intent. Resolve columns via `GET /reports/columns` if the user only named a type. "bu ay/geçen ay" → dynamic date filter. (For ad-hoc/free queries prefer the `querybuilder_*` tools.)
|
|
167
209
|
|
|
168
210
|
### Sürüm oluştur (release) — `create_release_list` → `POST /api/releases/lists`
|
|
169
211
|
|
|
@@ -179,9 +221,46 @@ SysTa returns structured errors. Recover deterministically instead of giving up.
|
|
|
179
221
|
- If it's genuinely user-owned (a `title`, a date, an approver name, a secret), **ask the user for exactly that field** — quote the human-friendly meaning, not the raw key.
|
|
180
222
|
- Retry once after a fix. If it fails again with the same field, surface it to the user rather than looping.
|
|
181
223
|
2. **`UNEXPECTED_FILE_FIELD` / `UNKNOWN_PARAM`** — you sent a flag/field the endpoint doesn't accept. Re-check with `describe_endpoint` and drop it. (Web/UI-only fields aren't valid on the API surface.)
|
|
182
|
-
3. **`404` on a `:requestNumber` / `:id`** — the handle didn't resolve. Run the matching list/search (`list_requests`, `list_projects`, `
|
|
183
|
-
4. **ID resolution chain (general).** Most mutations need an id the user gave you as a name
|
|
224
|
+
3. **`404` on a `:requestNumber` / `:id`** — the handle didn't resolve. Run the matching list/search (`list_requests`, `list_projects`, or `systa_api_call` `GET /users?search=`, `GET /tags/search`) to map name/number → id, then retry. Never hard-code an id.
|
|
225
|
+
4. **ID resolution chain (general).** Most mutations need an id the user gave you as a name. There are NO dedicated lookup tools — use `systa_api_call`: company → `GET /companies`, team → `GET /teams` (name→`teamCode`), user → `GET /users?search=` (name→`userId`, "ben/bana"→self via who-am-i), status → `GET /statuses` (name→`statusId`), tag → `GET /tags/search`, board → request's kanban hierarchy (`get_request_board`; requestId→`boardId`→`columnId`). Resolve first, act second.
|
|
184
226
|
5. **`401/403`** — expired or out-of-scope key. Don't retry blindly; tell the user the auth/permission problem (in their language) and stop. For `403 permission`, name the capability the key lacks; don't attempt a privilege workaround.
|
|
185
227
|
6. **`409 conflict`** (optimistic concurrency on cards/edits) — re-read the entity to get the latest `updatedAt`, then retry the mutation once with the fresh value; warn the user only if it conflicts again.
|
|
186
228
|
7. **Dual-path "list vs create" endpoints** (e.g. `POST /api/sla/definitions`, `POST /api/sla/standby-configs`) — an empty/filter-only body lists; a full body creates. Choose the body shape that matches the intent; don't accidentally create when the user asked to list.
|
|
187
229
|
8. **Multi-tenant safety.** Never carry an id across vendors. If a resolved id isn't found in the caller's vendor scope, treat it as not-found (step 3), not as a public-schema fallback.
|
|
230
|
+
|
|
231
|
+
## DX / Keşif Notları (talep 166 — prod'da doğrulanmış)
|
|
232
|
+
|
|
233
|
+
Gerçek AI kullanımında tespit edilen sürtünme noktaları; her biri canlı doğrulandı.
|
|
234
|
+
|
|
235
|
+
### Cevap boyutu / kompaktlık
|
|
236
|
+
|
|
237
|
+
- **Liste uçları ağırdır.** Geniş/wildcard anahtarda önce `list_capabilities({summary:true})` (oryantasyon, birkaç KB), sonra `list_capabilities({module:"<ad>"})` ile derinleş.
|
|
238
|
+
- **Yalnızca SAYI gerekiyorsa** tüm veriyi çekme: `list_*` araçlarını `limit=1` ile çağır, `pagination.total` oku.
|
|
239
|
+
|
|
240
|
+
### /api ön-eki
|
|
241
|
+
|
|
242
|
+
- `systa_api_call` base'i zaten `/api` içerir. Katalog yolları `/api/...` gösterse de path `/requests` ya da `/api/requests` olabilir — baştaki tek `/api` otomatik strip edilir.
|
|
243
|
+
|
|
244
|
+
### Analitik / rapor / agregasyon (ham veri çekip elle hesaplama)
|
|
245
|
+
|
|
246
|
+
- `querybuilder_execute_query` server-side groupBy/aggregations yapar (queryConfig: `fields`/`groupBy`/`aggregations:[{field,function}]`/`having`).
|
|
247
|
+
- HAZIR PIVOT: `reports/preview` (reportType=user/request/sla/organization/project/lifecycle) per-satır pivot/ortalama döner — örn. reportType=user → kullanıcı başına `avgResolutionHours`/`totalResolved`/`slaSuccessRate`.
|
|
248
|
+
- Proje metrikleri: `get_project_analytics` + `get_project_health`; plan: `get_plan_critical_path`.
|
|
249
|
+
|
|
250
|
+
### Talep açma — `description` = ilk yorum (commentNo 1)
|
|
251
|
+
|
|
252
|
+
- Verdiğin `description` talebin **ilk yorumu** olarak materyalize edilir.
|
|
253
|
+
- **Tablo/zengin içerik** için: `systa_api_call POST /requests` ile `descriptionJson` alanına TipTap doc (tablo dahil) gönder — bu da comment #1 olur. **Ayrıca ayrı yorum EKLEME** (çift yorum olur). İçeriği yalnız ayrı yorumda istiyorsan `description` boş bırak.
|
|
254
|
+
|
|
255
|
+
### Kanban
|
|
256
|
+
|
|
257
|
+
- Talep listesindeki `taskCount` ile kanban kart sayısı **farklı olabilir** (subtask/arşiv/parent kapsamı). Kesin liste/sayım için `get_request_board` (full board) otoritedir.
|
|
258
|
+
- **Tamamlanma zamanı:** `completedAt`/`completedBy` yalnız full board'da gelir; lightweight `cards/list` bunları İÇERMEZ (yalnız `isCompleted`). Süre/performans analizinde full board kullan.
|
|
259
|
+
|
|
260
|
+
### Plan modları
|
|
261
|
+
|
|
262
|
+
- `POST/PUT /plan/modes` filtreleme **mod değil her BOARD içindeki** `filterCriteria` ile yapılır. Geçerli anahtarlar: `assignedTo`, `statusId`/`excludeStatusId`, `projectId` (id dizileri), `includeStakeholderRequests` (bool). describe opak gösterir; emin değilsen mevcut bir modu `GET /plan/modes` ile incele.
|
|
263
|
+
|
|
264
|
+
### Proje alt-ağacı
|
|
265
|
+
|
|
266
|
+
- Kök projenin tüm alt projelerini kapsamak için önce hiyerarşiyi al (`get_project`/`list_projects`), kök + tüm alt-proje id'lerini `projectId` DİZİSİ olarak geç.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "systa-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "MCP stdio server for SysTa (Talep Takip Sistemi) — lets AI agents (Claude, Codex) use the SysTa REST API via a scoped API key. Zero npm dependencies (native Node).",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "server.js",
|
package/server.js
CHANGED
|
@@ -29,7 +29,9 @@ const path = require('node:path');
|
|
|
29
29
|
|
|
30
30
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
31
31
|
const SERVER_NAME = 'systa-mcp';
|
|
32
|
-
|
|
32
|
+
// Tek kaynak: surum package.json'dan okunur (drift onleme — server.js/SKILL.md/package.json
|
|
33
|
+
// ayni surumu gosterir). npm yayinlanan pakete package.json'i daima dahil eder.
|
|
34
|
+
const SERVER_VERSION = require('./package.json').version;
|
|
33
35
|
|
|
34
36
|
// Server-seviye rehber — her oturumda AI ajanin context'ine girer (kisa tutulur).
|
|
35
37
|
// Ajana SysTa'yi nasil kesfedip kullanacagini ogretir; detay lazy araclardadir.
|
|
@@ -41,9 +43,12 @@ const INSTRUCTIONS = [
|
|
|
41
43
|
'kapsam disi araclar listede yer almaz; listede gorundugu halde kapsam disi kalan bir',
|
|
42
44
|
'cagri yine de sunucu tarafinda 403 doner. Bir isi yapmadan ONCE:',
|
|
43
45
|
' 1) `list_capabilities` cagir — anahtarin erisebildigi modul/endpoint katalogunu',
|
|
44
|
-
' (scope-filtreli) gorursun.
|
|
45
|
-
'
|
|
46
|
-
'
|
|
46
|
+
' (scope-filtreli) gorursun. GENIS/WILDCARD anahtarda ONCE `list_capabilities({summary:true})`',
|
|
47
|
+
' cagir (oryantasyon + modul indeksi, birkac KB; tam katalog yuzlerce KB olabilir), sonra',
|
|
48
|
+
' ilgili modul icin `list_capabilities({module:"<ad>"})` ile derinles. Yetkisiz endpoint listede GORUNMEZ. Yanit AYRICA SysTa',
|
|
49
|
+
' platform OZETI (overview), TR GLOSSARY, her modulun ACIKLAMASINI (description/concepts)',
|
|
50
|
+
' ve SENIN kullanicini (user) icerir — "ben / bana ata / uzerimdeki" niyetinde assignedTo',
|
|
51
|
+
' icin user.id BURADAN gelir. Kullanicinin dogal dilini (talep/gorev/efor/pano/durum/atanan...)',
|
|
47
52
|
' bunlarla dogru modul ve endpoint ile eslestir.',
|
|
48
53
|
' 2) `describe_module(module)` (opsiyonel) — bir modulun (orn. request/kanban/plan) amacini,',
|
|
49
54
|
' kavramlarini ve cagirabilecegin endpointleri topluca gorursun.',
|
|
@@ -70,7 +75,38 @@ const INSTRUCTIONS = [
|
|
|
70
75
|
' respond_to_approval/list_pending_approvals), proje (create_project/get_project/update_project)',
|
|
71
76
|
' ve dosya ekleri (download_request_file / upload_file_to) ozel araclardir — `systa_api_call` JSON-only oldugundan',
|
|
72
77
|
' binary tasiyamaz; diger her endpoint icin `systa_api_call` (method+path) kullan — path ve',
|
|
73
|
-
' govdeyi describe_endpoint ciktisindan kur.',
|
|
78
|
+
' govdeyi describe_endpoint ciktisindan kur (path icin /api on-eki opsiyonel, otomatik normalize edilir).',
|
|
79
|
+
'',
|
|
80
|
+
'RAPOR / SAYIM / DAGILIM / TOPLAMA niyetinde: list_requests`i elle sayfalayip client-side TOPLAMA YAPMA.',
|
|
81
|
+
' Sunucu-tarafli rapor araclari VARDIR: querybuilder_ai_generate_query (dogal dil -> sorgu),',
|
|
82
|
+
' querybuilder_execute_query (server-side groupBy/sayim/filtre), querybuilder_describe_fields,',
|
|
83
|
+
' querybuilder_saved_query, querybuilder_schedule_report. Proje metrikleri icin get_project_analytics',
|
|
84
|
+
' (burndown/velocity/cumulative-flow...) ve get_project_health (saglik/risk/sla). Plan icin',
|
|
85
|
+
' get_plan_critical_path. Bunlar katalogda da var — once list_capabilities ile dogrula.',
|
|
86
|
+
' HAZIR PIVOT: reports/preview (reportType=user|request|sla|organization|project|lifecycle)',
|
|
87
|
+
' sunucu-tarafli pivot/ortalama dondurur — orn. reportType=user kullanici basina',
|
|
88
|
+
' avgResolutionHours/totalResolved/slaSuccessRate verir; "ortalama cozum suresi / kim kaç /',
|
|
89
|
+
' dağılım" gibi sorularda ham veri cekip elle hesaplama, bu pivotu kullan.',
|
|
90
|
+
' Yalnizca SAYI gerekiyorsa (kac kayit) tum veriyi cekme: list_* araclarini limit=1 ile',
|
|
91
|
+
' cagir ve donen pagination.total degerini oku.',
|
|
92
|
+
'',
|
|
93
|
+
'KANBAN (gorev panosu): bir talebin taskCount`u (talep listesinde) ile kanban kart sayisi',
|
|
94
|
+
' FARKLI olabilir — taskCount, subtask/arsiv/parent kartlari farkli kapsar. Kesin kart',
|
|
95
|
+
' listesi/sayimi ve TAMAMLANMA bilgisi icin get_request_board (full /kanban) otoritedir:',
|
|
96
|
+
' kartta completedAt + completedBy + isCompleted + completionPercentage gelir. Lightweight',
|
|
97
|
+
' cards/list hafiftir ama completedAt/completedBy ICERMEZ (yalniz isCompleted) — tamamlanma',
|
|
98
|
+
' suresi / performans analizi icin get_request_board kullan, her karti tek tek cekme.',
|
|
99
|
+
'',
|
|
100
|
+
'PLAN modlari (POST/PUT /plan/modes): filtreleme MOD seviyesinde degil, her BOARD icindeki',
|
|
101
|
+
' filterCriteria ile yapilir (mode.boards[].filterCriteria). describe_endpoint bunu opak',
|
|
102
|
+
' (unknown/additionalProperties) gosterir; gecerli anahtarlar request filtre semasidir:',
|
|
103
|
+
' assignedTo (user id dizisi), statusId / excludeStatusId (durum id dizisi), projectId',
|
|
104
|
+
' (proje id dizisi), includeStakeholderRequests (bool). Emin degilsen mevcut bir modu',
|
|
105
|
+
' GET /plan/modes ile inceleyip formati ornek al — bastan tahmin etme.',
|
|
106
|
+
'',
|
|
107
|
+
'PROJE alt-agaci: bir kok projenin TUM alt projelerini kapsamak icin once hiyerarsiyi al',
|
|
108
|
+
' (get_project / list_projects ile parent-child), sonra kok + tum alt-proje id`lerini',
|
|
109
|
+
' projectId DIZISI olarak gec (tek kok id alt agaci otomatik kapsamaz).',
|
|
74
110
|
].join('\n');
|
|
75
111
|
|
|
76
112
|
const BASE_URL = (process.env.SYSTA_API_BASE_URL || '').replace(/\/+$/, '');
|
|
@@ -79,6 +115,17 @@ const VENDOR_CODE = process.env.SYSTA_VENDOR_CODE || '';
|
|
|
79
115
|
|
|
80
116
|
const ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
81
117
|
|
|
118
|
+
// list_capabilities/describe_endpoint advertise route patterns WITH the "/api"
|
|
119
|
+
// prefix (e.g. "/api/requests"), but BASE_URL already ends in /api — so a path
|
|
120
|
+
// copied verbatim would double-prefix into /api/api -> ROUTE_NOT_FOUND. Strip a
|
|
121
|
+
// single leading "/api" segment so both "/api/requests" and "/requests" work.
|
|
122
|
+
const stripApiPrefix = (p) => {
|
|
123
|
+
if (typeof p !== 'string') {
|
|
124
|
+
return p;
|
|
125
|
+
}
|
|
126
|
+
return /^\/api(\/|$)/.test(p) ? p.slice(4) || '/' : p;
|
|
127
|
+
};
|
|
128
|
+
|
|
82
129
|
// Diagnostics MUST go to stderr — stdout is the JSON-RPC channel.
|
|
83
130
|
const logErr = (message) => process.stderr.write(`[${SERVER_NAME}] ${message}\n`);
|
|
84
131
|
|
|
@@ -334,12 +381,51 @@ const shapeCompact = (caps) => ({
|
|
|
334
381
|
})),
|
|
335
382
|
});
|
|
336
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Index-only view: orientation (overview/glossary/user) + module names with
|
|
386
|
+
* endpoint COUNTS, but NO per-endpoint detail. For a wildcard '*' key the full
|
|
387
|
+
* compact catalog is ~250KB (26 modules / 698 endpoints) which overflows agent
|
|
388
|
+
* buffers; this summary is a few KB. Drill into one module with module='x'.
|
|
389
|
+
*/
|
|
390
|
+
const shapeSummary = (caps) => ({
|
|
391
|
+
sessionType: caps.sessionType,
|
|
392
|
+
...(caps.user ? { user: caps.user } : {}),
|
|
393
|
+
scopes: caps.scopes,
|
|
394
|
+
isSuperAdmin: caps.isSuperAdmin,
|
|
395
|
+
...(caps.overview ? { overview: caps.overview } : {}),
|
|
396
|
+
...(caps.glossary ? { glossary: caps.glossary } : {}),
|
|
397
|
+
moduleCount: caps.moduleCount,
|
|
398
|
+
endpointCount: caps.endpointCount,
|
|
399
|
+
hint: 'Summary view — endpoints omitted. Call list_capabilities({module:"<name>"}) or describe_module to see a module\'s endpoints.',
|
|
400
|
+
modules: (caps.modules || []).map((m) => ({
|
|
401
|
+
module: m.module,
|
|
402
|
+
...(m.description ? { description: m.description } : {}),
|
|
403
|
+
endpointCount: (m.endpoints || []).length,
|
|
404
|
+
})),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const shapeOneModule = (caps, target) => {
|
|
408
|
+
const t = String(target).toLowerCase();
|
|
409
|
+
const full = shapeCompact(caps);
|
|
410
|
+
const mod = (full.modules || []).find((m) => String(m.module).toLowerCase() === t);
|
|
411
|
+
return {
|
|
412
|
+
...full,
|
|
413
|
+
modules: mod ? [mod] : [],
|
|
414
|
+
...(mod
|
|
415
|
+
? {}
|
|
416
|
+
: {
|
|
417
|
+
note: `Module "${target}" not found or not in this key scope.`,
|
|
418
|
+
availableModules: (full.modules || []).map((m) => m.module),
|
|
419
|
+
}),
|
|
420
|
+
};
|
|
421
|
+
};
|
|
422
|
+
|
|
337
423
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
338
424
|
const TOOLS = [
|
|
339
425
|
{
|
|
340
426
|
name: 'list_requests',
|
|
341
427
|
description:
|
|
342
|
-
'List/search SysTa requests (talepler) with pagination and rich filters: status, assignee, company, category, department, priority, free-text. "uzerimdeki isler / my open work" => assignedTo=<me> (resolve via list_capabilities user.id) +
|
|
428
|
+
'List/search SysTa requests (talepler) with pagination and rich filters: status, assignee, company, category, department, priority, free-text. "uzerimdeki acik isler / my open work" => assignedTo=<me> (resolve via list_capabilities user.id) + openOnly:true (one call — excludes final/"Tamamlandi" statuses automatically). Multiple statuses: statusId can be an array [1,3,5]. Returns only requests the API key scope permits. For filters beyond these, use systa_api_call GET /requests (this endpoint rejects unknown params with a 400 naming the valid set).',
|
|
343
429
|
inputSchema: {
|
|
344
430
|
type: 'object',
|
|
345
431
|
additionalProperties: false,
|
|
@@ -352,7 +438,16 @@ const TOOLS = [
|
|
|
352
438
|
},
|
|
353
439
|
offset: { type: 'integer', minimum: 0, description: 'Pagination offset' },
|
|
354
440
|
search: { type: 'string', description: 'Free-text search' },
|
|
355
|
-
statusId: {
|
|
441
|
+
statusId: {
|
|
442
|
+
type: ['integer', 'array'],
|
|
443
|
+
items: { type: 'integer' },
|
|
444
|
+
description: 'Filter by a status id, or an array of status ids for multiple',
|
|
445
|
+
},
|
|
446
|
+
openOnly: {
|
|
447
|
+
type: 'boolean',
|
|
448
|
+
description:
|
|
449
|
+
'true => only open (non-final) requests — auto-resolves to the non-final status ids (excludes "Tamamlandi/kapatildi")',
|
|
450
|
+
},
|
|
356
451
|
assignedTo: {
|
|
357
452
|
type: 'integer',
|
|
358
453
|
description: 'Filter by assignee user id (use own id for "my work")',
|
|
@@ -363,31 +458,55 @@ const TOOLS = [
|
|
|
363
458
|
priority: { type: 'string', description: 'Filter by priority key/level' },
|
|
364
459
|
},
|
|
365
460
|
},
|
|
366
|
-
handler: (args) =>
|
|
367
|
-
|
|
461
|
+
handler: async (args) => {
|
|
462
|
+
let statusId = args.statusId;
|
|
463
|
+
// openOnly: resolve the NON-final status ids and filter to them. GET /requests
|
|
464
|
+
// accepts statusId as an array (validator normalizes a comma list); it does NOT
|
|
465
|
+
// support excludeStatusIds, so we INCLUDE the open ones rather than exclude final.
|
|
466
|
+
if (args.openOnly) {
|
|
467
|
+
const st = await systaFetch('GET', '/statuses').catch(() => null);
|
|
468
|
+
const rows = st && st.ok ? (st.data && (st.data.data || st.data)) || [] : [];
|
|
469
|
+
const openIds = (Array.isArray(rows) ? rows : [])
|
|
470
|
+
.filter((s) => s && !s.isFinal)
|
|
471
|
+
.map((s) => s.id);
|
|
472
|
+
if (openIds.length > 0) {
|
|
473
|
+
statusId = openIds;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return systaFetch('GET', '/requests', {
|
|
368
477
|
query: {
|
|
369
478
|
limit: args.limit ?? 20,
|
|
370
479
|
offset: args.offset,
|
|
371
480
|
search: args.search,
|
|
372
|
-
statusId
|
|
481
|
+
statusId,
|
|
373
482
|
assignedTo: args.assignedTo,
|
|
374
483
|
companyId: args.companyId,
|
|
375
484
|
categoryId: args.categoryId,
|
|
376
485
|
departmentId: args.departmentId,
|
|
377
486
|
priority: args.priority,
|
|
378
487
|
},
|
|
379
|
-
})
|
|
488
|
+
});
|
|
489
|
+
},
|
|
380
490
|
},
|
|
381
491
|
{
|
|
382
492
|
name: 'get_request',
|
|
383
|
-
description:
|
|
493
|
+
description:
|
|
494
|
+
'Get a single SysTa request (talep) by its request number, including its details. Param name is "requestNumber" to match the other request tools (add_request_comment/change_request_status/assign_request/update_request); "id" is accepted as an alias.',
|
|
384
495
|
inputSchema: {
|
|
385
496
|
type: 'object',
|
|
386
497
|
additionalProperties: false,
|
|
387
|
-
|
|
388
|
-
|
|
498
|
+
properties: {
|
|
499
|
+
requestNumber: { type: 'integer', description: 'Request number (the id shown in lists)' },
|
|
500
|
+
id: { type: 'integer', description: 'Alias for requestNumber (back-compat)' },
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
handler: async (args) => {
|
|
504
|
+
const rn = args.requestNumber ?? args.id;
|
|
505
|
+
if (rn === undefined || rn === null) {
|
|
506
|
+
throw new Error('requestNumber (or id) is required');
|
|
507
|
+
}
|
|
508
|
+
return systaFetch('GET', `/requests/${encodeURIComponent(rn)}`);
|
|
389
509
|
},
|
|
390
|
-
handler: (args) => systaFetch('GET', `/requests/${encodeURIComponent(args.id)}`),
|
|
391
510
|
},
|
|
392
511
|
{
|
|
393
512
|
name: 'list_projects',
|
|
@@ -411,11 +530,11 @@ const TOOLS = [
|
|
|
411
530
|
{
|
|
412
531
|
name: 'create_request',
|
|
413
532
|
description:
|
|
414
|
-
'Create a SysTa request (talep). Requires the "request.create" scope. Returns the created request under data.request. PROACTIVE (recommended): call prepare_create_request(companyId) FIRST -- it returns the active form\'s required fields AND valid ids (statuses/categories/organizations) so you fill them up front and avoid the 400 roundtrip. The required set is DYNAMIC: title/companyId/statusId are the baseline (+assignedTo
|
|
533
|
+
'Create a SysTa request (talep). Requires the "request.create" scope. Returns the created request under data.request. PROACTIVE (recommended): call prepare_create_request(companyId) FIRST -- it returns the active form\'s required fields AND valid ids (statuses/categories/organizations) so you fill them up front and avoid the 400 roundtrip. The required set is DYNAMIC: title/companyId/statusId are the baseline (+ at least one assignment: assignedTo OR assignedTeams), but this vendor\'s ACTIVE REQUEST FORM may require more system fields (e.g. categoryId, organizationId). If you skip prepare_, on a missing field the API returns 400 VALIDATION_FAILED naming it via details[].fieldKey -- add it and retry. For fields beyond this tool, use systa_api_call POST /requests. ONEMLI (talep 166 #60): verdigin `description` talebin ILK YORUMU (commentNo 1) olarak materyalize edilir; ZENGIN icerik istiyorsan (tablo/kod blogu/bicimli metin) `description` yerine systa_api_call POST /requests ile `descriptionJson` alanina TipTap doc gonder (tablo dahil) -- bu da comment #1 olur. Yani tabloyu talep ACARKEN descriptionJson icinde ver; AYRICA ayri bir yorum EKLEME (yoksa cift yorum olusur). Icerigi yalnizca ayri yorumda istiyorsan description bos birak.',
|
|
415
534
|
inputSchema: {
|
|
416
535
|
type: 'object',
|
|
417
536
|
additionalProperties: false,
|
|
418
|
-
required: ['title', 'companyId', 'statusId'
|
|
537
|
+
required: ['title', 'companyId', 'statusId'],
|
|
419
538
|
properties: {
|
|
420
539
|
title: { type: 'string', description: 'Request title' },
|
|
421
540
|
description: { type: 'string', description: 'Request description (plain text)' },
|
|
@@ -423,7 +542,18 @@ const TOOLS = [
|
|
|
423
542
|
statusId: { type: 'integer', description: 'Initial status id' },
|
|
424
543
|
assignedTo: {
|
|
425
544
|
type: 'integer',
|
|
426
|
-
description:
|
|
545
|
+
description:
|
|
546
|
+
'Assignee user id. SysTa requires at least ONE assignment: provide assignedTo (a user) OR assignedTeams (teams).',
|
|
547
|
+
},
|
|
548
|
+
assignedTeams: {
|
|
549
|
+
type: 'array',
|
|
550
|
+
items: { type: 'integer' },
|
|
551
|
+
description:
|
|
552
|
+
'Assignee team ids (alternative/addition to assignedTo; at least one of assignedTo/assignedTeams is required).',
|
|
553
|
+
},
|
|
554
|
+
channel: {
|
|
555
|
+
type: 'string',
|
|
556
|
+
description: "Source channel; defaults to 'API' for AI/MCP-created requests.",
|
|
427
557
|
},
|
|
428
558
|
categoryId: {
|
|
429
559
|
type: 'integer',
|
|
@@ -443,7 +573,9 @@ const TOOLS = [
|
|
|
443
573
|
description: args.description ?? '',
|
|
444
574
|
companyId: args.companyId,
|
|
445
575
|
statusId: args.statusId,
|
|
446
|
-
|
|
576
|
+
channel: args.channel ?? 'API',
|
|
577
|
+
...(args.assignedTo != null ? { assignedTo: args.assignedTo } : {}),
|
|
578
|
+
...(args.assignedTeams != null ? { assignedTeams: args.assignedTeams } : {}),
|
|
447
579
|
...(args.categoryId != null ? { categoryId: args.categoryId } : {}),
|
|
448
580
|
...(args.organizationId != null ? { organizationId: args.organizationId } : {}),
|
|
449
581
|
},
|
|
@@ -534,11 +666,17 @@ const TOOLS = [
|
|
|
534
666
|
} catch {
|
|
535
667
|
// non-fatal — baseline fallback below
|
|
536
668
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
669
|
+
// create_request ALSO requires API-layer invariants that are NOT part of
|
|
670
|
+
// the active form schema (title/companyId/statusId/assignedTo). The form
|
|
671
|
+
// only describes custom/system form fields, so always merge these in —
|
|
672
|
+
// otherwise the helper omits e.g. assignedTo and the first create_request
|
|
673
|
+
// still 400s on a missing field (the exact roundtrip this tool prevents).
|
|
674
|
+
const BASELINE_REQUIRED = ['title', 'companyId', 'statusId', 'assignedTo'];
|
|
675
|
+
const have = new Set(requiredFields.map((f) => f.fieldKey));
|
|
676
|
+
for (const k of BASELINE_REQUIRED) {
|
|
677
|
+
if (!have.has(k)) {
|
|
678
|
+
requiredFields.push({ fieldKey: k, isSystem: true });
|
|
679
|
+
}
|
|
542
680
|
}
|
|
543
681
|
const need = new Set(requiredFields.map((f) => f.fieldKey));
|
|
544
682
|
|
|
@@ -579,7 +717,7 @@ const TOOLS = [
|
|
|
579
717
|
companyId,
|
|
580
718
|
requiredFields,
|
|
581
719
|
options,
|
|
582
|
-
note: 'Fill create_request with these required fields; resolve each id from options[fieldKey]. statusId.isFinal=true => a tamamla/kapat status. A missing option list = out of this key scope; ask the user or use systa_api_call.',
|
|
720
|
+
note: 'Fill create_request with ALL these required fields; resolve each id from options[fieldKey]. assignedTo is the assignee user id — for "bana ata / assign to me" use your own user.id from list_capabilities (who-am-i). statusId.isFinal=true => a tamamla/kapat status. A missing option list = out of this key scope; ask the user or use systa_api_call.',
|
|
583
721
|
},
|
|
584
722
|
};
|
|
585
723
|
},
|
|
@@ -1687,7 +1825,7 @@ const TOOLS = [
|
|
|
1687
1825
|
{
|
|
1688
1826
|
name: 'systa_api_call',
|
|
1689
1827
|
description:
|
|
1690
|
-
'Generic SysTa REST API call. Use for any endpoint not covered by a specific tool (create/update/comment, etc.). The API key scope is enforced server-side: out-of-scope calls return 403. path is relative to the API base (e.g. "/requests/42/comments").',
|
|
1828
|
+
'Generic SysTa REST API call. Use for any endpoint not covered by a specific tool (create/update/comment, etc.). The API key scope is enforced server-side: out-of-scope calls return 403. path is relative to the API base (e.g. "/requests/42/comments"). You may paste a path verbatim from list_capabilities/describe_endpoint even though those include the "/api" prefix — a leading "/api" is auto-stripped (the base URL already ends in /api), so "/api/requests" and "/requests" both work.',
|
|
1691
1829
|
inputSchema: {
|
|
1692
1830
|
type: 'object',
|
|
1693
1831
|
additionalProperties: false,
|
|
@@ -1711,7 +1849,7 @@ const TOOLS = [
|
|
|
1711
1849
|
if (typeof args.path !== 'string' || !args.path.startsWith('/')) {
|
|
1712
1850
|
throw new Error('path must start with "/"');
|
|
1713
1851
|
}
|
|
1714
|
-
return systaFetch(method, args.path, { query: args.query, body: args.body });
|
|
1852
|
+
return systaFetch(method, stripApiPrefix(args.path), { query: args.query, body: args.body });
|
|
1715
1853
|
},
|
|
1716
1854
|
},
|
|
1717
1855
|
{
|
|
@@ -1798,7 +1936,7 @@ const TOOLS = [
|
|
|
1798
1936
|
if (filePaths.length < 1 || filePaths.length > 50) {
|
|
1799
1937
|
throw new Error('filePaths must contain between 1 and 50 local file paths');
|
|
1800
1938
|
}
|
|
1801
|
-
return systaUploadMultipart(args.path, method, filePaths);
|
|
1939
|
+
return systaUploadMultipart(stripApiPrefix(args.path), method, filePaths);
|
|
1802
1940
|
},
|
|
1803
1941
|
},
|
|
1804
1942
|
{
|
|
@@ -1807,15 +1945,38 @@ const TOOLS = [
|
|
|
1807
1945
|
'Discover what THIS API key can actually do. Returns a scope-filtered catalog of ' +
|
|
1808
1946
|
'modules and endpoints the key is permitted to call (out-of-scope endpoints are not ' +
|
|
1809
1947
|
'shown), each with its HTTP methods, safety class (read_only/idempotent/mutating/' +
|
|
1810
|
-
'destructive) and required permission.
|
|
1811
|
-
'
|
|
1812
|
-
|
|
1813
|
-
|
|
1948
|
+
'destructive) and required permission. Also returns SysTa overview, TR glossary and your ' +
|
|
1949
|
+
'user (who-am-i). Call this FIRST. For a broad key the full catalog can be large: pass ' +
|
|
1950
|
+
'summary=true to get only orientation + module names with endpoint counts (a few KB), then ' +
|
|
1951
|
+
'pass module="<name>" to drill into one module. No args = full compact catalog.',
|
|
1952
|
+
inputSchema: {
|
|
1953
|
+
type: 'object',
|
|
1954
|
+
additionalProperties: false,
|
|
1955
|
+
properties: {
|
|
1956
|
+
summary: {
|
|
1957
|
+
type: 'boolean',
|
|
1958
|
+
description: 'true => orientation + module index only (no per-endpoint detail)',
|
|
1959
|
+
},
|
|
1960
|
+
module: {
|
|
1961
|
+
type: 'string',
|
|
1962
|
+
description:
|
|
1963
|
+
'return only this module\'s endpoints (e.g. "request", "project", "query-builder")',
|
|
1964
|
+
},
|
|
1965
|
+
},
|
|
1966
|
+
},
|
|
1967
|
+
handler: async (args) => {
|
|
1814
1968
|
const res = await systaFetch('GET', '/api-keys/me/capabilities');
|
|
1815
1969
|
if (!res.ok) {
|
|
1816
1970
|
return res;
|
|
1817
1971
|
}
|
|
1818
|
-
|
|
1972
|
+
const caps = unwrapCapabilities(res);
|
|
1973
|
+
if (args && args.module) {
|
|
1974
|
+
return shapeOneModule(caps, args.module);
|
|
1975
|
+
}
|
|
1976
|
+
if (args && args.summary) {
|
|
1977
|
+
return shapeSummary(caps);
|
|
1978
|
+
}
|
|
1979
|
+
return shapeCompact(caps);
|
|
1819
1980
|
},
|
|
1820
1981
|
},
|
|
1821
1982
|
{
|
|
@@ -2037,14 +2198,63 @@ const send = (message) => process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
|
2037
2198
|
const reply = (id, result) => ({ jsonrpc: '2.0', id, result });
|
|
2038
2199
|
const errorReply = (id, code, message) => ({ jsonrpc: '2.0', id, error: { code, message } });
|
|
2039
2200
|
|
|
2201
|
+
/**
|
|
2202
|
+
* Validate tool-call arguments against the tool's declared inputSchema BEFORE
|
|
2203
|
+
* dispatch. The server advertises additionalProperties:false but did not enforce
|
|
2204
|
+
* it, so a hallucinated arg (e.g. isCompleted on list_requests) was silently
|
|
2205
|
+
* dropped and produced a confidently-wrong result. Reject unknown args + missing
|
|
2206
|
+
* required fields with a clear, actionable message so the agent self-corrects.
|
|
2207
|
+
* @returns {string|null} error message, or null if valid
|
|
2208
|
+
*/
|
|
2209
|
+
const validateArgs = (tool, args) => {
|
|
2210
|
+
const schema = tool && tool.inputSchema;
|
|
2211
|
+
if (!schema || schema.type !== 'object') {
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
const props = schema.properties || {};
|
|
2215
|
+
const a = args && typeof args === 'object' ? args : {};
|
|
2216
|
+
if (schema.additionalProperties === false) {
|
|
2217
|
+
const unknown = Object.keys(a).filter((k) => !(k in props));
|
|
2218
|
+
if (unknown.length > 0) {
|
|
2219
|
+
const valid = Object.keys(props).join(', ') || '(none)';
|
|
2220
|
+
return `Unknown argument(s): ${unknown.join(', ')}. This tool accepts only: ${valid}. For filters beyond these use systa_api_call (check describe_endpoint for the valid set).`;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
2224
|
+
const missing = required.filter((k) => a[k] === undefined || a[k] === null);
|
|
2225
|
+
if (missing.length > 0) {
|
|
2226
|
+
return `Missing required argument(s): ${missing.join(', ')}.`;
|
|
2227
|
+
}
|
|
2228
|
+
return null;
|
|
2229
|
+
};
|
|
2230
|
+
|
|
2040
2231
|
const handleToolCall = async (id, params) => {
|
|
2041
2232
|
const name = params && params.name;
|
|
2042
2233
|
const tool = TOOL_MAP.get(name);
|
|
2043
2234
|
if (!tool) {
|
|
2044
2235
|
return errorReply(id, -32602, `Unknown tool: ${name}`);
|
|
2045
2236
|
}
|
|
2237
|
+
const args = (params && params.arguments) || {};
|
|
2238
|
+
// Enforce the declared inputSchema (additionalProperties:false + required) so
|
|
2239
|
+
// unknown/missing args surface a clear error instead of being silently dropped.
|
|
2240
|
+
const argError = validateArgs(tool, args);
|
|
2241
|
+
if (argError) {
|
|
2242
|
+
return reply(id, {
|
|
2243
|
+
content: [
|
|
2244
|
+
{
|
|
2245
|
+
type: 'text',
|
|
2246
|
+
text: JSON.stringify(
|
|
2247
|
+
{ ok: false, error: 'INVALID_ARGUMENTS', message: argError },
|
|
2248
|
+
null,
|
|
2249
|
+
2,
|
|
2250
|
+
),
|
|
2251
|
+
},
|
|
2252
|
+
],
|
|
2253
|
+
isError: true,
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2046
2256
|
try {
|
|
2047
|
-
const result = await tool.handler(
|
|
2257
|
+
const result = await tool.handler(args);
|
|
2048
2258
|
const isError = result && result.ok === false;
|
|
2049
2259
|
return reply(id, {
|
|
2050
2260
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
@@ -2211,6 +2421,7 @@ module.exports = {
|
|
|
2211
2421
|
TOOL_MAP,
|
|
2212
2422
|
TOOL_REQUIRED_PERMISSION,
|
|
2213
2423
|
visibleTools,
|
|
2424
|
+
validateArgs,
|
|
2214
2425
|
installSkill,
|
|
2215
2426
|
runCli,
|
|
2216
2427
|
systaFetch,
|
|
@@ -2218,6 +2429,8 @@ module.exports = {
|
|
|
2218
2429
|
systaUploadMultipart,
|
|
2219
2430
|
handleMessage,
|
|
2220
2431
|
shapeCompact,
|
|
2432
|
+
shapeSummary,
|
|
2433
|
+
stripApiPrefix,
|
|
2221
2434
|
unwrapCapabilities,
|
|
2222
2435
|
parseContentDispositionFilename,
|
|
2223
2436
|
sanitizeFileName,
|