systa-mcp 1.3.0 → 1.5.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.
Files changed (4) hide show
  1. package/README.md +24 -3
  2. package/SKILL.md +215 -56
  3. package/package.json +1 -1
  4. package/server.js +355 -50
package/README.md CHANGED
@@ -101,14 +101,14 @@ claude mcp add systa \
101
101
 
102
102
  ## Araçlar (tools)
103
103
 
104
- Toplam **64 araç** vardır; `tools/list` yanıtı anahtarın kapsamına göre filtrelenir
104
+ Toplam **79 araç** vardır; `tools/list` yanıtı anahtarın kapsamına göre filtrelenir
105
105
  (bkz. "Scope-aware araç listesi"). Aşağıdakiler en sık kullanılan çekirdek araçlardır —
106
106
  tam katalog `list_capabilities` ile keşfedilir.
107
107
 
108
108
  | Araç | Açıklama |
109
109
  | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
110
110
  | `list_requests` | Talepleri listele (limit/offset/search/statusId) — `request.read` |
111
- | `get_request` | Tek bir talebi id ile getir — `request.read` |
111
+ | `get_request` | Tek bir talebi talep numarasıyla getir — `request.tabs.general.view` |
112
112
  | `list_projects` | Projeleri listele — `project.read` |
113
113
  | `create_request` | Talep oluştur (title/description/companyId/statusId/assignedTo) — `request.create` |
114
114
  | `add_request_comment` | Talebe yorum ekle (düz metin otomatik TipTap'e sarılır) — `request.comment.create` + `request.tabs.comments.view` |
@@ -117,12 +117,33 @@ tam katalog `list_capabilities` ile keşfedilir.
117
117
  | `upload_file_to` | Yerel dosyaları multipart olarak ek diye yükler (talep/proje/görev kartı/orphan) — `file.upload` |
118
118
  | `list_capabilities` | **Bu anahtar ne yapabilir?** — scope-filtreli modül/endpoint kataloğu (yetkisiz endpoint görünmez); her metot için safety class + gereken izin. Ayrıca SysTa platform özeti (overview), TR glossary ve her modülün açıklamasını içerir |
119
119
  | `describe_module` | Bir modülün (örn. request/kanban/plan) amacı, kavramları ve çağrılabilir endpoint'leri — kullanıcı niyetini doğru modüle eşlemek için (oryantasyon) |
120
- | `describe_endpoint` | Tek endpoint detayı: alan tipleri (fields), güvenlik sınıfı, açıklama — çağrı gövdesini kurmadan önce |
120
+ | `describe_endpoint` | Tek endpoint detayı: alan tipleri, güvenlik sınıfı ve tam HTTP Response Contract v2 — çağrı gövdesini kurmadan ve yanıtı zincirlemeden önce |
121
121
 
122
122
  `systa_api_call` ile, özel bir aracı olmayan herhangi bir endpoint çağrılabilir
123
123
  (örn. `POST /requests`, `POST /requests/42/comments`). Anahtarın scope'u dışındaki
124
124
  çağrılar `403` döner.
125
125
 
126
+ ### Alt form hazırlama
127
+
128
+ Alt form (`sub-forms`), ana talep formu (`form`) ile aynı modül değildir. Ajan önce
129
+ `list_capabilities({module:"sub-forms"})`, ardından kullanacağı uçlar için
130
+ `describe_endpoint` çağırmalıdır. Önerilen taslak akışı:
131
+
132
+ 1. `POST /sub-forms` ile `isPublic:false` bir form kabuğu oluşturun. `parentFormId`
133
+ backend tarafından şirketin aktif talep formundan çözülür; istemci göndermemelidir.
134
+ 2. Bölüm, alan ve kuralları ayrı ayrı yazmak yerine
135
+ `PUT /sub-forms/:id/save` ile tek transaction'da kaydedin. Yeni öğelerde `tempId`,
136
+ alan-bölüm ilişkisinde `sectionTempId` kullanılır; yanıt gerçek id haritalarını verir.
137
+ 3. Kullanım biçimini yalnız `embed` veya `request_attach` binding'iyle tanımlayın.
138
+ 4. `GET /sub-forms/:id/preview` HTML değil `{form, sections, fields, preview:true}`
139
+ JSON payload'ı döndürür. Query/rule/title/correlation test uçları kalıcı ayar yazmaz.
140
+ 5. Taslak özetini kullanıcıya göstermeden ve açık onay almadan publish/public erişim,
141
+ allowed origin, webhook, periodic veya correlation özelliklerini etkinleştirmeyin.
142
+
143
+ Tam alan türleri, atomik save gövdesi, binding kararları ve hata kurtarma akışı paketle
144
+ birlikte kurulan `SKILL.md` içindedir. Sürüm yükselttikten sonra skill'i yenilemek için
145
+ `npx -y systa-mcp@latest --install-skill` komutunu yeniden çalıştırın.
146
+
126
147
  ### Dosya ekleri (download_request_file / upload_file_to)
127
148
 
128
149
  `systa_api_call` JSON-only olduğundan binary taşıyamaz — dosya ekleri için bu iki
package/SKILL.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 1.3.0
2
+ version: 1.5.0
3
3
  name: systa
4
4
  description: |
5
5
  Drive SysTa (Talep Takip Sistemi / SysTa) — a
@@ -15,12 +15,14 @@ description: |
15
15
  "bana/şuna ata", "yorum ekle", "durumu değiştir /
16
16
  tamamla / kapat", "üzerimdeki işler", "proje aç",
17
17
  "kanban kartı ekle", "onaya sun", "onayla/reddet",
18
- "izin talebi", "rapor al / dışa aktar", "sürüm
19
- oluştur". Use when (EN): "create a SysTa request",
18
+ "izin talebi", "alt form hazırla / oluştur", "rapor
19
+ al / dışa aktar", "sürüm oluştur". Use when (EN):
20
+ "create a SysTa request",
20
21
  "log a ticket", "assign / reassign", "add a comment",
21
22
  "move/close the ticket", "show my open work", "create
22
23
  project", "add task card", "request approval",
23
- "approve/reject", "export report", "create release".
24
+ "approve/reject", "design/create a sub-form", "export
25
+ report", "create release".
24
26
  Multi-tenant (vendor schema) aware; resolves names →
25
27
  ids; honors dynamic required fields. NOT for: editing
26
28
  SysTa source code, running psql/DB migrations directly,
@@ -87,6 +89,7 @@ Short shared vocabulary. Use these words with the user; never expose raw REST pa
87
89
  | SLA | SLA | Süre taahhütleri (ilk yanıt, çözüm). Instance bir talebe bağlanır; pause/resume edilebilir. |
88
90
  | Onay | Approval | Toplu onaya gönderme + onayla/reddet; plan onayı ve talep onayı ayrı akışlar. |
89
91
  | Sürüm | Release | Yazılım sürüm yönetimi: liste → item → onay → Go/No-Go → deployment workflow. |
92
+ | Alt form | Sub-form | Talep formuna gömülen veya talep akışına bağlanan, bölüm/alan/kural/binding içeren ek veri toplama şeması. Ana istek formu (`form`) ile aynı şey değildir. |
90
93
  | Vendor şema | Vendor schema | Multi-tenant izolasyon; her vendor ayrı schema (`vendor_xxx`). Public referans alınmaz. |
91
94
 
92
95
  ### Anahtar kelime → modül eşlemesi (keyword index)
@@ -96,56 +99,58 @@ yanıtındaki `glossary` alias'larının ayna kopyasıdır (MCP yokken de geçer
96
99
  ASCII'dir — eşleştirmede aksanı yok say. Modülü bulduktan sonra `describe_module(modül)`
97
100
  veya `describe_endpoint` ile gövdeyi netleştir.
98
101
 
99
- | Doğal dil (alias) | Modül | Birincil giriş |
100
- | ------------------------------------------------------------ | --------------- | ----------------------------------------------------------------------------------------------- |
101
- | talep, talepler, is, destek talebi | request | list_requests / create_request |
102
- | geciken, overdue, gecikmis, sla ihlali, deadline gecmis | request/sla | list_requests(overdue:true / slaBreached:true) |
103
- | toplu guncelle, hepsini, birden cok talep | request | bulk_update_requests |
104
- | kullanici bul, isim cozumle, kime, hangi ahmet | user | resolve_user_by_name / list_users |
105
- | sirket bul, kategori bul, ekip bul, id cozumle, lookup | multiple | list_lookup(kind:'company'/'category'/'team'/...) |
106
- | projeyi klonla, projeyi cogalt, duplicate project | project | duplicate_project |
107
- | snapshot fark, baseline karsilastir, plan diff | plan | compare_plan_baseline |
108
- | yorumlar, ekler, degisiklik gecmisi, denetim | request | list_request_comments / list_request_files / list_request_history |
109
- | yorum yaz, tablo, referans, #125, ##1809, kod blogu, baslik | request | add_request_comment — govde TipTap JSON; tablo/liste commentTextJson ile, referanslar DUZ METIN |
110
- | gorev, task, kart, card | kanban | manage_request_card / move_request_card |
111
- | pano, board, kanban / kolon, column, asama, liste | kanban | get_request_board / create_request_board |
112
- | durum gecisi onizle, izinli gecisler, hangi duruma gecebilir | request | list_allowed_transitions |
113
- | plan modu, sprint kapsami, plan mode setup | plan | create_plan_mode |
114
- | durum, oncelik, atanan, planla, denetim, gecmis | request | get_request / list_requests |
115
- | tahmini efor, planlanan sure (estimatedEffort) | request | update_request (TAHMIN alani, defter DEGIL) |
116
- | efor gir, efor yaz, calistigim sure, efor defteri, mesai | effort | log_effort / list_effort_targets / list_effort_entries |
117
- | eksik efor girisim var mi, efor uyumu | effort | get_effort_compliance |
118
- | proje, klonla | project | list_projects / create_project |
119
- | onaya sunma, snapshot, sprint, sprinti kapat, optimize et | plan | submit_plan_for_approval / create_plan_baseline |
120
- | onay, onayla, reddet | approvals | list_pending_approvals / respond_to_approval |
121
- | onay tipleri, kime onaya gonderelim | approvals | list_approval_types |
122
- | red nedeni, neden reddedildi, gecerli red sebepleri | approvals | list_rejection_reasons |
123
- | bu talep neden dondu, neden yazamiyorum, kilitli alan | approvals | get_request_freeze |
124
- | sla, sure taahhudu | sla | (sla endpoints) |
125
- | release, surum, go/no-go, deployment | release | create_release_list / add_release_item (listeyi ID ile hedefle) |
126
- | surum numarasi, versionLabel, ayni gun iki liste | release | create_release_list -> id + versionLabel (tarih DEGIL) |
127
- | musaitlik, izin (leave), vekil | user/automation | systa_api_call /user-availability, /user-leaves |
128
- | vendor, sirket, musteri | company | systa_api_call GET /companies |
129
- | organizasyon birimi, departman, bolum, birim | department | GET /api/departments |
130
- | takim, ekip, team | team | GET /api/teams |
131
- | yetki paketi, izin (permission) | security | (bundle/permission endpoints) |
132
- | alt form, sub-form | forms | systa_api_call GET /forms/:id |
133
- | rapor, raporlama, disa aktar, export, excel, csv | report | systa_api_call /reports/preview,/reports/export |
134
- | ozel sorgu, sorgu, query builder, ad-hoc, veri cek | query-builder | querybuilder_describe_fields / \_execute_query |
135
- | hesap tablosu, gridpad, tablo, excel, spreadsheet, hucre | spreadsheet | GET /api/spreadsheets |
136
- | dosya, ek, attachment, yukle, upload, indir, download | file | upload_file_to / download_request_file |
137
- | bildirim, notification, uyari, okundu, push | notification | GET /api/notifications |
138
- | yetenek, beceri, skill, otomatik etiketleme | skill | GET /api/skills |
139
- | duyuru, announcement, bildiri, ilan | announcement | GET /api/announcements |
140
- | urun guncellemesi, surum notu, yenilikler, changelog | product-update | GET /api/product-updates |
141
- | not, kisisel not, hatirlatma | note | GET /api/dashboard/notes |
142
- | yer imi, favori, bookmark, kisayol | bookmark | GET /api/dashboard/bookmarks |
143
- | degerlendirme, puanlama, memnuniyet, geri bildirim | ratings | POST /api/ratings |
144
- | anket, oylama, karar alalim, ekibe soralim, secenek sun | decisions | systa_api_call POST /requests/:n/decisions |
145
- | github, repo, pull request, commit, issue aktar | github | systa_api_call /github/me/{connection,repos,import,push} |
146
- | gosterge paneli, dashboard, widget, ozet, grafik, panel | dashboard | GET /api/dashboard/init |
147
-
148
- > Bu tablo, katalogdaki ~25 modülün kullanıcı-yüzeyli olanlarından bir alt küme kapsar. Listede olmayan
102
+ | Doğal dil (alias) | Modül | Birincil giriş |
103
+ | ------------------------------------------------------------ | ----------------- | ----------------------------------------------------------------------------------------------- |
104
+ | talep, talepler, is, destek talebi | request | list_requests / create_request |
105
+ | geciken, overdue, gecikmis, sla ihlali, deadline gecmis | request/sla | list_requests(overdue:true / slaBreached:true) |
106
+ | toplu guncelle, hepsini, birden cok talep | request | bulk_update_requests |
107
+ | kullanici bul, isim cozumle, kime, hangi ahmet | user | resolve_user_by_name / list_users |
108
+ | sirket bul, kategori bul, ekip bul, id cozumle, lookup | multiple | list_lookup(kind:'company'/'category'/'team'/...) |
109
+ | projeyi klonla, projeyi cogalt, duplicate project | project | duplicate_project |
110
+ | snapshot fark, baseline karsilastir, plan diff | plan | compare_plan_baseline |
111
+ | yorumlar, ekler, degisiklik gecmisi, denetim | request | list_request_comments / list_request_files / list_request_history |
112
+ | yorum yaz, tablo, referans, #125, ##1809, kod blogu, baslik | request | add_request_comment — govde TipTap JSON; tablo/liste commentTextJson ile, referanslar DUZ METIN |
113
+ | gorev, task, kart, card | kanban | manage_request_card / move_request_card |
114
+ | pano, board, kanban / kolon, column, asama, liste | kanban | get_request_board / create_request_board |
115
+ | durum gecisi onizle, izinli gecisler, hangi duruma gecebilir | request | list_allowed_transitions |
116
+ | plan modu, sprint kapsami, plan mode setup | plan | create_plan_mode |
117
+ | durum, oncelik, atanan, planla, denetim, gecmis | request | get_request / list_requests |
118
+ | tahmini efor, planlanan sure (estimatedEffort) | request | update_request (TAHMIN alani, defter DEGIL) |
119
+ | efor gir, efor yaz, calistigim sure, efor defteri, mesai | effort | log_effort / list_effort_targets / list_effort_entries |
120
+ | eksik efor girisim var mi, efor uyumu | effort | get_effort_compliance |
121
+ | proje, klonla | project | list_projects / create_project |
122
+ | onaya sunma, snapshot, sprint, sprinti kapat, optimize et | plan | submit_plan_for_approval / create_plan_baseline |
123
+ | onay, onayla, reddet | approvals | list_pending_approvals / respond_to_approval |
124
+ | onay tipleri, kime onaya gonderelim | approvals | list_approval_types |
125
+ | red nedeni, neden reddedildi, gecerli red sebepleri | approvals | list_rejection_reasons |
126
+ | bu talep neden dondu, neden yazamiyorum, kilitli alan | approvals | get_request_freeze |
127
+ | sla, sure taahhudu | sla | (sla endpoints) |
128
+ | release, surum, go/no-go, deployment | release | create_release_list / add_release_item (listeyi ID ile hedefle) |
129
+ | surum numarasi, versionLabel, ayni gun iki liste | release | create_release_list -> id + versionLabel (tarih DEGIL) |
130
+ | musaitlik, uygunluk | user-availability | systa_api_call GET /user-availability |
131
+ | izin (leave), izin hakkı, takım izin takvimi | leave-management | systa_api_call /leave-management, /user-leaves |
132
+ | vekil, delegate, proxy | automation-rules | systa_api_call /automation-rules |
133
+ | vendor, sirket, musteri | company | systa_api_call GET /companies |
134
+ | organizasyon birimi, departman, bolum, birim | company | GET /api/departments |
135
+ | takim, ekip, team | team | GET /api/teams |
136
+ | yetki paketi, izin (permission) | security | (bundle/permission endpoints) |
137
+ | alt form, sub-form, subform, ek form, mini form | sub-forms | systa_api_call GET /sub-forms/:id |
138
+ | rapor, raporlama, disa aktar, export, excel, csv | report | systa_api_call /reports/preview,/reports/export |
139
+ | ozel sorgu, sorgu, query builder, ad-hoc, veri cek | query-builder | querybuilder_describe_fields / \_execute_query |
140
+ | hesap tablosu, gridpad, tablo, excel, gridPad, hucre | gridpad | GET /api/gridpads |
141
+ | dosya, ek, attachment, yukle, upload, indir, download | file | upload_file_to / download_request_file |
142
+ | bildirim, notification, uyari, okundu, push | notification | GET /api/notifications |
143
+ | yetenek, beceri, skill, otomatik etiketleme | skills | GET /api/skills |
144
+ | duyuru, announcement, bildiri, ilan | announcements | GET /api/announcements |
145
+ | urun guncellemesi, surum notu, yenilikler, changelog | product-updates | GET /api/product-updates |
146
+ | not, kisisel not, hatirlatma | dashboard | GET /api/dashboard/notes |
147
+ | yer imi, favori, bookmark, kisayol | dashboard | GET /api/dashboard/bookmarks |
148
+ | degerlendirme, puanlama, memnuniyet, geri bildirim | ratings | POST /api/ratings |
149
+ | anket, oylama, karar alalim, ekibe soralim, secenek sun | decisions | systa_api_call POST /requests/:n/decisions |
150
+ | github, repo, pull request, commit, issue aktar | github | systa_api_call /github/me/{connection,repos,import,push} |
151
+ | gosterge paneli, dashboard, widget, ozet, grafik, panel | dashboard | GET /api/dashboard/init |
152
+
153
+ > Bu tablo, dinamik katalogdaki kullanıcı-yüzeyli modüllerin bir alt kümesini kapsar. Listede olmayan
149
154
  > bir kelime için `list_capabilities` çağır — anahtarının erişebildiği güncel modül/alias
150
155
  > kümesini döndürür (kapsam-filtreli; yetkisiz modül görünmez).
151
156
 
@@ -155,7 +160,7 @@ veya `describe_endpoint` ile gövdeyi netleştir.
155
160
  2. **No jargon.** Don't narrate "calling describe_endpoint", "POST /api/requests", "resolving statusId". Speak in the product's user verbs.
156
161
  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.
157
162
  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.
158
- 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.
163
+ 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. For a main request form use the `form` module; for an alt form use `describe_endpoint('/api/sub-forms/:id','GET')` and then read `GET /api/sub-forms/:id`. Never substitute `/forms/:id` for an alt form.
159
164
  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.
160
165
  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.
161
166
 
@@ -164,9 +169,33 @@ veya `describe_endpoint` ile gövdeyi netleştir.
164
169
  You don't memorize 600+ endpoints. Discover on demand.
165
170
 
166
171
  - **`list_capabilities`** — call when you need to find _which_ module/intent serves the user's verb, or when the user asks "ne yapabilirsin / what can you do". Returns scope-filtered modules (this key's reachable surface), each with a short summary, category, safety class and required permission. Use it to pick the right tool before acting; never claim a capability the catalog doesn't list for this key.
167
- - **`describe_endpoint`** — call when you've picked a tool and need its exact required fields, body shape, enums and defaults before submitting. Prefer this over guessing a body. It returns the field schema (`fieldKey`, type, required, enum, default) so you can pre-fill dynamic required fields and avoid `VALIDATION_FAILED`.
172
+ - **`describe_endpoint`** — call with both `path` and `method` when you've picked a tool and need its exact required fields, body shape, enums and defaults before submitting. Prefer this over guessing a body. It returns the field schema plus the verified HTTP Response Contract v2 (status, transport, content type, envelope and data/body schema); use returned IDs, handles and pagination fields for the next step instead of guessing them.
173
+ - **`describe_module`** — call after you've mapped the user's intent to a module. Returns the module's purpose, its key concepts, its endpoints, and **`entryPoints`**: the calls that need **no id up front**. A chain always starts at an entry point — calling an id-bearing endpoint first just returns 404/400.
168
174
  - **Rule of thumb:** ambiguous intent → `list_capabilities` first; chosen intent but unsure of body → `describe_endpoint`; clear intent + known body → act directly. For REST fallback, the equivalents are `GET /api/api-keys/me/capabilities` (preview of reachable endpoints) and the per-endpoint field schema returned by the MCP `describe_endpoint`.
169
175
 
176
+ ### Çağrı sırası (prerequisites)
177
+
178
+ A compact listing marks a method with `hasPrerequisites: true` when it needs an id you must obtain **first**. `describe_endpoint` returns the detail:
179
+
180
+ ```json
181
+ "prerequisites": [
182
+ {
183
+ "parameter": "requestId",
184
+ "collection": "/api/requests",
185
+ "producedBy": [
186
+ { "path": "/api/requests", "method": "GET", "field": "id", "evidence": "response-field" }
187
+ ]
188
+ }
189
+ ]
190
+ ```
191
+
192
+ - `source` is `"path"` when the id goes into the URL, `"body"` when it is a **required body field** (e.g. `POST /api/approvals` requires `requestId` in the body — it never appears in the path).
193
+ - `producedBy` lists the endpoints that **produce** that id; the first candidate carries the strongest evidence.
194
+ - `evidence: "response-field"` — the producer's response contract really contains `field`; chain it directly.
195
+ - `evidence: "collection-path"` — the link comes from the REST path structure (the producer's response schema is dynamic); call the producer and read the id off the returned record.
196
+
197
+ So the flow is: **user intent → `list_capabilities` (module) → `describe_module` (`entryPoints`) → `describe_endpoint` (fields + `prerequisites`) → walk the chain top-down → perform the action.** Never invent an id; always take it from a producer endpoint.
198
+
170
199
  ## Çekirdek iş akışları (core workflows)
171
200
 
172
201
  Each maps a P0 user verb to a tool/endpoint, with the opinionated defaults from the catalog. Resolve ids first, act, then summarize in the user's language.
@@ -326,6 +355,136 @@ Gövdede geçen şu düz metin tokenları otomatik olarak **tıklanabilir rozete
326
355
 
327
356
  - 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.
328
357
 
358
+ ### Alt form hazırla / oluştur (design a sub-form) — `systa_api_call` + `sub-forms`
359
+
360
+ Alt form, ana talep formu değildir. `form` modülü ana talep şemasını; `sub-forms`
361
+ modülü ise bağımsız/gömülü ek veri toplama şemasını yönetir. Alt form; form meta
362
+ bilgisi, bölümler, alanlar, kurallar ve nerede çalışacağını belirleyen binding'lerden
363
+ oluşur. Dedicated create aracı yoktur; keşif ve tüm çağrılar `systa_api_call` ile yapılır.
364
+
365
+ #### 1. İhtiyacı kısa bir forma çevir
366
+
367
+ Kullanıcıdan doğal dilde şu kararları çıkar; yalnız gerçekten eksik olan **tek** kararı sor:
368
+
369
+ - Amaç ve görünen ad. `formKey`, `sectionKey`, `fieldKey` değerlerini ajan üretir:
370
+ küçük harf + rakam + alt çizgi, en az 2 karakter (`musteri_geri_bildirim` gibi).
371
+ - Kullanım biçimi: doğrudan/gömülü veri toplayıp talep açma → formMode
372
+ 'create_request' + bindingType 'embed'; mevcut talep içinde görev/ek iş formu →
373
+ formMode 'create_task' + bindingType 'request_attach'; ikisi de gerekiyorsa formMode
374
+ 'both'. Binding türü yalnız 'embed' veya 'request_attach' olabilir.
375
+ - Bölümler ve alanlar. Alan türleri:
376
+ `text`, `textarea`, `number`, `currency`, `email`, `phone`, `date`, `datetime`,
377
+ `select`, `multiselect`, `checkbox`, `radio`, `file`, `hidden`, `custom_regex`,
378
+ `image`, `signature`, `location`, `rating`, `switch`, `richtext`.
379
+ Veri türü `string|number|boolean|date|file|json`; seçenekli alanlarda `optionsConfig`,
380
+ zorunlu alanlarda `isRequired:true`, iki kolon düzeninde `colSpan:1|2` kullan.
381
+ - Otomasyon, dış erişim veya yayın isteği açıkça söylenmediyse kural, webhook,
382
+ periyodik çalışma, public erişim, captcha secret ya da allowed origin ekleme.
383
+ Güvenli taslak varsayılanı `isPublic:false` ve yayında-değil durumudur.
384
+
385
+ Etiket/başlık gibi çevrilebilir alanları katalog şemasının istediği i18n nesnesiyle
386
+ gönder (örn. `{tr:'E-posta',en:'Email'}`). Kullanıcının verdiği mevcut id'leri başka
387
+ vendor'da yeniden kullanma; company/team/user/status/category/board/formRule adlarını
388
+ önce bu vendor içinde çöz.
389
+
390
+ #### 2. Uçları ve güncel şemayı keşfet
391
+
392
+ 1. `list_capabilities({module:'sub-forms'})` çağır. Ayrıca binding ve gönderi işi
393
+ varsa `describe_module('sub-form-bindings')` / `describe_module('sub-form-submissions')`
394
+ kullan. Modül görünmüyorsa kapsam dışıdır; endpoint uydurma.
395
+ 2. En az şu uçları `describe_endpoint(path,method)` ile oku:
396
+ `POST /api/sub-forms`, `PUT /api/sub-forms/:id/save`, `GET /api/sub-forms/:id/preview`
397
+ ve seçilen binding gerekiyorsa `POST /api/sub-forms/:id/bindings`.
398
+ 3. Mevcut bir form değişiyorsa önce `GET /api/sub-forms/:id` oku; kullanıcı açıkça
399
+ kaldır demedikçe mevcut section/field/rule kimliklerini koru.
400
+
401
+ #### 3. Önce taslak şemayı oluştur, sonra atomik kaydet
402
+
403
+ 1. `POST /api/sub-forms` ile yalnız temel formu oluştur: `formName`, üretilmiş
404
+ `formKey`, amaçtan seçilen `formMode`, `isPublic:false`. Kullanım biçimi belliyse
405
+ `initialBindingType` da gönderilebilir; 'request_attach' binding'de `formRuleId`
406
+ henüz yoksa binding pasif oluşur ve daha sonra tamamlanır. `parentFormId` gönderme:
407
+ backend bunu şirketin aktif ana talep formundan server-authoritative çözer ve
408
+ create/update/save sırasında istemci değerini yok sayar.
409
+ 2. Dönen alt form `id`'sini al. Bölüm/alan/kuralı tek tek yazmak yerine
410
+ `PUT /api/sub-forms/:id/save` kullan; bu işlem tek transaction'da kaydeder ve
411
+ form sürümünü artırır. Gövde:
412
+
413
+ ```json
414
+ {
415
+ "form": { "formName": "Müşteri Geri Bildirimi", "formMode": "create_request" },
416
+ "sections": [
417
+ {
418
+ "tempId": "sec_contact",
419
+ "sectionKey": "iletisim",
420
+ "title": { "tr": "İletişim" },
421
+ "displayOrder": 0
422
+ }
423
+ ],
424
+ "fields": [
425
+ {
426
+ "tempId": "fld_email",
427
+ "sectionTempId": "sec_contact",
428
+ "fieldKey": "email",
429
+ "fieldType": "email",
430
+ "dataType": "string",
431
+ "label": { "tr": "E-posta" },
432
+ "isRequired": true,
433
+ "displayOrder": 0,
434
+ "colSpan": 2
435
+ }
436
+ ],
437
+ "rules": [],
438
+ "deletedSectionIds": [],
439
+ "deletedFieldIds": [],
440
+ "deletedRuleIds": []
441
+ }
442
+ ```
443
+
444
+ Yeni bileşenlerde `tempId`, alan-bölüm ilişkisinde `sectionTempId` kullan. Yanıttaki
445
+ `sectionIdMap`, `fieldIdMap`, `ruleIdMap` gerçek id'leri verir. Mevcut bileşende
446
+ `id` gönder. `deleted*Ids` listelerine yalnız kullanıcının açıkça kaldırdığı mevcut
447
+ id'leri koy; listede olmayan öğeleri “eksik geldi” diye silme.
448
+
449
+ 3. İlk create'te binding kurulmadıysa `POST /api/sub-forms/:id/bindings` çağır.
450
+ 'embed' site/portal içinde doğrudan çalışır. 'request_attach' mevcut talep akışına
451
+ `formRuleId` ile bağlanır; rule çözülene kadar pasif kalabilir. Rastgele talep/proje
452
+ id'sini binding diye gönderme.
453
+
454
+ #### 4. Doğrula; kullanıcı onayı olmadan yayınlama
455
+
456
+ - `GET /api/sub-forms/:id` ile kaydedilmiş tanımı, ardından
457
+ `GET /api/sub-forms/:id/preview` ile `{form,sections,fields,preview:true}` önizleme
458
+ payload'ını oku. Bu uç HTML/render edilmiş ekran döndürmez; görünümü istemci çizer.
459
+ - Dinamik query alanında `POST /api/sub-forms/:id/fields/:fieldId/test-query` çağrısı
460
+ yapılandırmayı kaydetmez ama tanımlı prefix-set/regex veya sandboxed read-only SQL'i
461
+ gerçekten çalıştırır. Yerleşik doğrulama ve lookup setlerini önce
462
+ `GET /api/sub-forms/query/prefix-sets` ile keşfet. Kurallar için
463
+ `POST /api/sub-forms/:id/rules/test` side-effect üretmeyen dry-run'dır.
464
+ - Kullanıcıya ad, kullanım biçimi, bölüm/alan listesi, zorunlu alanlar, binding ve
465
+ varsa kuralları tek özetle göster. `PATCH /api/sub-forms/:id/publish` yalnız açık
466
+ “yayınla/devreye al” onayından sonra çağrılır. Taslak hazırlama isteği yayınlama
467
+ izni değildir. Yayından kaldırma da canlı akışı durdurur; önceden onay al.
468
+
469
+ #### 5. İleri özellikleri yalnız ihtiyaç varsa ekle
470
+
471
+ - Public/embed: allowed origins tam origin biçimindedir (`https://ornek.com`, trailing
472
+ slash yok). Kullanıcı domaini ve public erişimi açıkça istemeden ekleme/açma.
473
+ - Başlık şablonu: kaydetmeden önce `POST /api/sub-forms/:id/title-preview` ile canlı
474
+ gönderim işlemcisine eşdeğer önizleme al.
475
+ - Korelasyon: önce `GET/POST /api/sub-forms/correlation-groups`; bir alt form yalnız
476
+ tek gruba üye olabilir. Etkin ayarda `groupId`, en az bir `matchField` ve kimlik rolü
477
+ (`customer_id|phone|email`) gerekir. `POST /api/sub-forms/:id/correlation/preview`
478
+ geçmiş gönderiler üstünde dry-run yapar; sonucu görmeden korelasyonu etkinleştirme.
479
+ - Webhook dış sisteme yan etki üretir. Hedef URL/olay kullanıcıya ait karardır; secret
480
+ isteme veya sohbette gösterme. Oluşturma, test ve secret rotasyonu öncesi onay al.
481
+
482
+ Hata kurtarma: `VALIDATION_FAILED` gelirse aynı ucu yeniden `describe_endpoint` ile
483
+ oku, yalnız hatalı alanı düzeltip bir kez dene. `403` için başka binding/modül yoluyla
484
+ yetki aşmaya çalışma. `409` formKey çakışmasında kullanıcı adını değiştirme; aynı
485
+ anlamı koruyan yeni bir key öner. Silme uçları soft-delete olsa bile güvenilir bir
486
+ restore ucu garanti edilmez; kaldırma öncesi kullanıcı onayı al.
487
+
329
488
  ### Rapor al / dışa aktar (report) — `systa_api_call` (no named tool)
330
489
 
331
490
  - 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.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "systa-mcp",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
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
@@ -50,10 +50,27 @@ const INSTRUCTIONS = [
50
50
  ' ve SENIN kullanicini (user) icerir — "ben / bana ata / uzerimdeki" niyetinde assignedTo',
51
51
  ' icin user.id BURADAN gelir. Kullanicinin dogal dilini (talep/gorev/efor/pano/durum/atanan...)',
52
52
  ' bunlarla dogru modul ve endpoint ile eslestir.',
53
- ' 2) `describe_module(module)` (opsiyonel) bir modulun (orn. request/kanban/plan) amacini,',
54
- ' kavramlarini ve cagirabilecegin endpointleri topluca gorursun.',
55
- ' 3) `describe_endpoint(path, method)` cagir o endpoint için alan tiplerini (fields),',
56
- ' guvenlik sinifini ve aciklamayi al; sonra cagri govdesini buna gore kur.',
53
+ ' 2) `describe_module(module)` — modulun amacini, kavramlarini, cagirabilecegin endpointleri VE',
54
+ ' `entryPoints`i verir. `entryPoints` ONCEDEN BIR KIMLIK GEREKTIRMEYEN cagrilardir:',
55
+ ' zincir HER ZAMAN buradan baslar. Kimlik isteyen bir ucu once denemek 404/400 uretir.',
56
+ ' 3) `describe_endpoint(path, method)` cagir alan tiplerini (fields), guvenlik sinifini,',
57
+ ' tam HTTP Response Contract v2 bilgisini ve `prerequisites` alanini al.',
58
+ '',
59
+ 'CAGRI SIRASI (prerequisites) — ONEMLI:',
60
+ ' Kompakt listede bir metodun `hasPrerequisites: true` olmasi, o ucun ONCE baska bir cagriyla',
61
+ ' elde edilmesi gereken bir kimlige ihtiyac duydugunu soyler. Ayrintiyi `describe_endpoint`',
62
+ ' verir:',
63
+ ' prerequisites: [{ parameter, collection, producedBy: [{ path, method, field?, evidence }] }]',
64
+ ' - `parameter` : gereken kimlik (orn. requestId).',
65
+ " - `source` : `path` -> URL'de doldurulur. `body` -> ZORUNLU govde alanidir ve",
66
+ " path'te HIC gorunmez (orn. POST /api/approvals govdesinde requestId).",
67
+ ' - `producedBy` : o kimligi URETEN uclar. Ilk aday en guclu kanitlidir.',
68
+ ' - `evidence` : `response-field` -> uretici ucun yanit semasinda `field` gercekten var,',
69
+ ' dogrudan zincirle. `collection-path` -> baglanti REST yapisindan kurulmus;',
70
+ ' uretici ucu cagir ve donen kayittan kimligi oku.',
71
+ ' Yani akis sudur: kullanicinin niyeti -> `list_capabilities` ile MODUL -> `describe_module`',
72
+ ' ile `entryPoints` -> gereken ucta `describe_endpoint` -> `prerequisites` zincirini yukaridan',
73
+ ' asagi cagir -> asil islemi yap. Kimlik UYDURMA; her zaman uretici uctan al.',
57
74
  '',
58
75
  'GUVENLIK SINIFI (safetyClass): read_only (guvenli okuma) | idempotent (tekrarlanabilir)',
59
76
  ' | mutating (durum degistirir) | destructive (geri-alinamaz — dikkatli ol, gerekirse onayla).',
@@ -77,6 +94,17 @@ const INSTRUCTIONS = [
77
94
  ' binary tasiyamaz; diger her endpoint icin `systa_api_call` (method+path) kullan — path ve',
78
95
  ' govdeyi describe_endpoint ciktisindan kur (path icin /api on-eki opsiyonel, otomatik normalize edilir).',
79
96
  '',
97
+ 'ALT FORM (sub-form) HAZIRLAMA: Ana talep formu `form`, ek/mini form ise `sub-forms`',
98
+ 'moduludur; `/forms/:id` alt-form detayi DEGILDIR. Once `list_capabilities({module:"sub-forms"})`,',
99
+ 'sonra POST `/sub-forms` + PUT `/sub-forms/:id/save` semalarini `describe_endpoint` ile oku.',
100
+ 'Taslagi POST `/sub-forms` ile isPublic=false olustur; section/field/rule degisikliklerini',
101
+ 'tempId + sectionTempId kullanarak TEK transaction olan PUT `/sub-forms/:id/save` ile kaydet.',
102
+ 'Binding yalniz `embed` veya `request_attach` olur. parentFormId server-authoritative oldugu',
103
+ 'icin gonderme. GET `/sub-forms/:id/preview` render edilmis HTML degil, form/section/field',
104
+ 'onizleme payloadidir. Query/rule/title/correlation preview uclari kalici config yazmaz.',
105
+ 'Kullaniciya taslak ozeti gostermeden ve acik onay almadan publish, public/origin, webhook,',
106
+ 'periodic veya korelasyon ozelligini etkinlestirme; silme/yayindan kaldirma icin de onay al.',
107
+ '',
80
108
  'YORUM YAZMA (comment) — ONEMLI, ZENGIN METIN SOZLESMESI:',
81
109
  ' Yorum govdesi DUZ METIN DEGIL, TipTap JSON dokumanidir: {type:"doc", content:[...]}.',
82
110
  ' `add_request_comment` duz metni tek paragrafa sarar — TABLO, BASLIK, LISTE, KOD BLOGU',
@@ -396,6 +424,292 @@ const systaUploadMultipart = async (apiPath, method, filePaths) => {
396
424
  const unwrapCapabilities = (res) =>
397
425
  res && res.data && res.data.data ? res.data.data : res && res.data;
398
426
 
427
+ const capabilityProjectionPath = (projection, filters = {}) => {
428
+ const query = new URLSearchParams({ projection });
429
+ for (const [key, value] of Object.entries(filters)) {
430
+ if (value !== undefined && value !== null && value !== '') {
431
+ query.set(key, String(value));
432
+ }
433
+ }
434
+ return `/api-keys/me/capabilities?${query.toString()}`;
435
+ };
436
+
437
+ const RESPONSE_TRANSPORTS = new Set([
438
+ 'json',
439
+ 'binary',
440
+ 'text',
441
+ 'sse',
442
+ 'redirect',
443
+ 'empty',
444
+ 'webhook_ack',
445
+ ]);
446
+ const RESPONSE_ENVELOPES = new Set([
447
+ 'systa_success_v1',
448
+ 'systa_error_v1',
449
+ 'raw_json',
450
+ 'legacy_error',
451
+ 'none',
452
+ ]);
453
+ const RESPONSE_SCHEMA_TYPES = new Set([
454
+ 'null',
455
+ 'boolean',
456
+ 'object',
457
+ 'array',
458
+ 'number',
459
+ 'integer',
460
+ 'string',
461
+ ]);
462
+ const RESPONSE_BINARY_DELIVERY_MODES = new Set(['buffer', 'stream']);
463
+ const isPlainObject = (value) =>
464
+ Boolean(value) && typeof value === 'object' && !Array.isArray(value);
465
+
466
+ const validSchemaVariants = (schema) => {
467
+ const variants = schema.oneOf || schema.anyOf;
468
+ if (variants === undefined) {
469
+ return { valid: true, variants: null };
470
+ }
471
+ return {
472
+ valid:
473
+ Array.isArray(variants) &&
474
+ variants.length > 0 &&
475
+ (!schema.oneOf || typeof schema.branchReason === 'string') &&
476
+ variants.every(isValidResponseJsonSchema),
477
+ variants,
478
+ };
479
+ };
480
+
481
+ const validSchemaTypes = (schema) => {
482
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
483
+ return {
484
+ valid: types.length > 0 && types.every((type) => RESPONSE_SCHEMA_TYPES.has(type)),
485
+ types,
486
+ };
487
+ };
488
+
489
+ const validObjectResponseSchema = (schema) => {
490
+ const properties = schema.properties;
491
+ const hasProperties = isPlainObject(properties) && Object.keys(properties).length > 0;
492
+ const dynamic =
493
+ (schema.additionalProperties === true || isPlainObject(schema.additionalProperties)) &&
494
+ typeof schema.dynamicReason === 'string';
495
+ const requiredValid =
496
+ schema.required === undefined ||
497
+ (Array.isArray(schema.required) &&
498
+ schema.required.every((key) => typeof key === 'string' && key in properties));
499
+ const childrenValid =
500
+ !hasProperties || Object.values(properties).every(isValidResponseJsonSchema);
501
+ const additionalValid =
502
+ !isPlainObject(schema.additionalProperties) ||
503
+ isValidResponseJsonSchema(schema.additionalProperties);
504
+ return (hasProperties || dynamic) && requiredValid && childrenValid && additionalValid;
505
+ };
506
+
507
+ function isValidResponseJsonSchema(schema) {
508
+ if (!isPlainObject(schema)) {
509
+ return false;
510
+ }
511
+ const variantResult = validSchemaVariants(schema);
512
+ if (!variantResult.valid) {
513
+ return false;
514
+ }
515
+ if (schema.type === undefined) {
516
+ return Boolean(
517
+ variantResult.variants || schema.$ref !== undefined || schema.const !== undefined,
518
+ );
519
+ }
520
+ const typeResult = validSchemaTypes(schema);
521
+ if (!typeResult.valid) {
522
+ return false;
523
+ }
524
+ if (typeResult.types.includes('array') && !isValidResponseJsonSchema(schema.items)) {
525
+ return false;
526
+ }
527
+ return !typeResult.types.includes('object') || validObjectResponseSchema(schema);
528
+ }
529
+
530
+ const hasValidResponseBasics = (response) =>
531
+ isPlainObject(response) &&
532
+ Number.isInteger(response.status) &&
533
+ response.status >= 100 &&
534
+ response.status <= 599 &&
535
+ RESPONSE_TRANSPORTS.has(response.transport) &&
536
+ RESPONSE_ENVELOPES.has(response.envelope);
537
+
538
+ const hasValidResponseContentType = (response) => {
539
+ const bodyless = response.transport === 'redirect' || response.transport === 'empty';
540
+ return bodyless
541
+ ? response.contentType === null && response.envelope === 'none'
542
+ : typeof response.contentType === 'string' && Boolean(response.contentType.trim());
543
+ };
544
+
545
+ const hasValidDynamicContentType = (response) => {
546
+ const hasDynamicMetadata =
547
+ response.dynamicContentType !== undefined ||
548
+ response.contentTypePattern !== undefined ||
549
+ response.contentTypeSource !== undefined;
550
+ if (response.contentType !== '*/*') {
551
+ return !hasDynamicMetadata;
552
+ }
553
+ if (
554
+ response.transport !== 'binary' ||
555
+ response.dynamicContentType !== true ||
556
+ typeof response.contentTypePattern !== 'string' ||
557
+ !response.contentTypePattern.trim() ||
558
+ typeof response.contentTypeSource !== 'string' ||
559
+ !response.contentTypeSource.trim()
560
+ ) {
561
+ return false;
562
+ }
563
+ try {
564
+ new RegExp(response.contentTypePattern);
565
+ return true;
566
+ } catch {
567
+ return false;
568
+ }
569
+ };
570
+
571
+ const hasValidJsonResponseSchema = (response) => {
572
+ if (response.transport !== 'json' && response.transport !== 'webhook_ack') {
573
+ return true;
574
+ }
575
+ const schema =
576
+ response.envelope === 'systa_success_v1' ? response.dataSchema : response.bodySchema;
577
+ return isValidResponseJsonSchema(schema);
578
+ };
579
+
580
+ const hasValidBinaryMetadata = (response) => {
581
+ if (response.transport !== 'binary' || response.headers === undefined) {
582
+ return true;
583
+ }
584
+ return (
585
+ isPlainObject(response.headers) &&
586
+ (response.headers.contentDisposition === undefined ||
587
+ typeof response.headers.contentDisposition === 'string')
588
+ );
589
+ };
590
+
591
+ const hasValidBinaryDeliveryModes = (response) => {
592
+ if (response.deliveryModes === undefined) {
593
+ return true;
594
+ }
595
+ return (
596
+ response.transport === 'binary' &&
597
+ Array.isArray(response.deliveryModes) &&
598
+ response.deliveryModes.length > 0 &&
599
+ new Set(response.deliveryModes).size === response.deliveryModes.length &&
600
+ response.deliveryModes.every((mode) => RESPONSE_BINARY_DELIVERY_MODES.has(mode))
601
+ );
602
+ };
603
+
604
+ const hasValidRedirectMetadata = (response) =>
605
+ response.transport !== 'redirect' ||
606
+ (typeof response.locationSource === 'string' && Boolean(response.locationSource.trim()));
607
+
608
+ const hasValidSseEvents = (response) =>
609
+ response.transport !== 'sse' ||
610
+ (Array.isArray(response.events) &&
611
+ response.events.length > 0 &&
612
+ response.events.every(
613
+ (event) =>
614
+ isPlainObject(event) &&
615
+ typeof event.name === 'string' &&
616
+ Boolean(event.name.trim()) &&
617
+ isValidResponseJsonSchema(event.dataSchema),
618
+ ));
619
+
620
+ const isValidResponseVariant = (response) =>
621
+ hasValidResponseBasics(response) &&
622
+ hasValidResponseContentType(response) &&
623
+ hasValidDynamicContentType(response) &&
624
+ hasValidJsonResponseSchema(response) &&
625
+ hasValidBinaryMetadata(response) &&
626
+ hasValidBinaryDeliveryModes(response) &&
627
+ hasValidRedirectMetadata(response) &&
628
+ hasValidSseEvents(response);
629
+
630
+ const isResponseContractV2 = (contract) => {
631
+ if (
632
+ !isPlainObject(contract) ||
633
+ contract.contractVersion !== 2 ||
634
+ !Array.isArray(contract.responses) ||
635
+ contract.responses.length === 0 ||
636
+ !contract.responses.every(isValidResponseVariant)
637
+ ) {
638
+ return false;
639
+ }
640
+ if (contract.errors === undefined) {
641
+ return true;
642
+ }
643
+ if (!isPlainObject(contract.errors) || !RESPONSE_ENVELOPES.has(contract.errors.envelope)) {
644
+ return false;
645
+ }
646
+ return (
647
+ contract.errors.statuses === undefined ||
648
+ (Array.isArray(contract.errors.statuses) &&
649
+ contract.errors.statuses.every(
650
+ (status) => Number.isInteger(status) && status >= 400 && status <= 599,
651
+ ))
652
+ );
653
+ };
654
+
655
+ const findCapabilityMethod = (caps, targetPath, targetMethod) => {
656
+ for (const module of caps.modules || []) {
657
+ const endpoint = (module.endpoints || []).find((item) => item.path === targetPath);
658
+ const method =
659
+ endpoint && (endpoint.methods || []).find((item) => item.method === targetMethod);
660
+ if (endpoint && method) {
661
+ return { module, endpoint, method };
662
+ }
663
+ }
664
+ return null;
665
+ };
666
+
667
+ const responseContractProblem = (responseSchema, targetMethod, targetPath) => {
668
+ if (responseSchema && responseSchema.contractVersion !== 2) {
669
+ return `Unsupported response contract version for ${targetMethod} ${targetPath}`;
670
+ }
671
+ if (!isResponseContractV2(responseSchema)) {
672
+ return `Missing or invalid Response Contract v2 for ${targetMethod} ${targetPath}`;
673
+ }
674
+ return null;
675
+ };
676
+
677
+ const describeCapabilityEndpoint = async (args) => {
678
+ const targetPath = String(args.path || '');
679
+ const targetMethod = String(args.method || '').toUpperCase();
680
+ const res = await systaFetch(
681
+ 'GET',
682
+ capabilityProjectionPath('endpoint', { path: targetPath, method: targetMethod }),
683
+ );
684
+ if (!res.ok) {
685
+ return res;
686
+ }
687
+ const found = findCapabilityMethod(unwrapCapabilities(res), targetPath, targetMethod);
688
+ if (!found) {
689
+ return {
690
+ ok: false,
691
+ error: `Endpoint not found or not permitted for this key: ${targetMethod} ${targetPath}`,
692
+ };
693
+ }
694
+ const contractProblem = responseContractProblem(
695
+ found.method.responseSchema,
696
+ targetMethod,
697
+ targetPath,
698
+ );
699
+ if (contractProblem) {
700
+ return { ok: false, error: contractProblem };
701
+ }
702
+ const { responseSchema, ...methodDetail } = found.method;
703
+ return {
704
+ module: found.module.module,
705
+ path: found.endpoint.path,
706
+ summary: found.endpoint.summary,
707
+ ...methodDetail,
708
+ responseContract: responseSchema,
709
+ responseContractStatus: 'verified',
710
+ };
711
+ };
712
+
399
713
  /**
400
714
  * Compact view of the capability catalog — drops field_schema/detail/scenario so
401
715
  * the per-turn payload stays small (lazy: use describe_endpoint for full detail).
@@ -433,7 +747,8 @@ const shapeCompact = (caps) => ({
433
747
  method: mm.method,
434
748
  safetyClass: mm.safetyClass,
435
749
  requiredPermission: mm.requiredPermission,
436
- hasFields: Boolean(mm.fields),
750
+ hasFields: Boolean(mm.hasFields || mm.fields),
751
+ hasResponseContract: Boolean(mm.hasResponseContract || mm.responseSchema),
437
752
  })),
438
753
  })),
439
754
  })),
@@ -457,11 +772,16 @@ const shapeSummary = (caps) => ({
457
772
  moduleCount: caps.moduleCount,
458
773
  endpointCount: caps.endpointCount,
459
774
  hint: 'Summary view — endpoints omitted. Call list_capabilities({module:"<name>"}) or describe_module to see a module\'s endpoints.',
460
- modules: (caps.modules || []).map((m) => ({
461
- module: m.module,
462
- ...(m.description ? { description: m.description } : {}),
463
- endpointCount: (m.endpoints || []).length,
464
- })),
775
+ modules: (caps.modules || []).map((m) => {
776
+ const paths = m.endpoints || [];
777
+ const methodCount = paths.reduce((sum, endpoint) => sum + (endpoint.methods || []).length, 0);
778
+ return {
779
+ module: m.module,
780
+ ...(m.description ? { description: m.description } : {}),
781
+ pathCount: m.pathCount ?? paths.length,
782
+ endpointCount: m.endpointCount ?? (methodCount || paths.length),
783
+ };
784
+ }),
465
785
  });
466
786
 
467
787
  const shapeOneModule = (caps, target) => {
@@ -3158,7 +3478,13 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3158
3478
  },
3159
3479
  },
3160
3480
  handler: async (args) => {
3161
- const res = await systaFetch('GET', '/api-keys/me/capabilities');
3481
+ let projectionPath = capabilityProjectionPath('compact');
3482
+ if (args && args.module) {
3483
+ projectionPath = capabilityProjectionPath('module', { module: args.module });
3484
+ } else if (args && args.summary) {
3485
+ projectionPath = capabilityProjectionPath('summary');
3486
+ }
3487
+ const res = await systaFetch('GET', projectionPath);
3162
3488
  if (!res.ok) {
3163
3489
  return res;
3164
3490
  }
@@ -3176,7 +3502,7 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3176
3502
  name: 'describe_module',
3177
3503
  description:
3178
3504
  'Orient on ONE SysTa module: returns its purpose (description, TR/EN), key concepts, ' +
3179
- 'and the endpoints THIS key can call within it (path + summary + method/safety). Use the ' +
3505
+ 'the endpoints THIS key can call within it (path + summary + method/safety), and entryPoints — the calls that need NO id up front, i.e. where a chain starts. Use the ' +
3180
3506
  'module name from list_capabilities (e.g. "request", "kanban", "plan", "sla"). Helps map a ' +
3181
3507
  "user's natural-language intent to the right module before describe_endpoint.",
3182
3508
  inputSchema: {
@@ -3188,7 +3514,10 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3188
3514
  },
3189
3515
  },
3190
3516
  handler: async (args) => {
3191
- const res = await systaFetch('GET', '/api-keys/me/capabilities');
3517
+ const res = await systaFetch(
3518
+ 'GET',
3519
+ capabilityProjectionPath('module', { module: args.module }),
3520
+ );
3192
3521
  if (!res.ok) {
3193
3522
  return res;
3194
3523
  }
@@ -3206,6 +3535,9 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3206
3535
  module: mod.module,
3207
3536
  description: mod.description,
3208
3537
  concepts: mod.concepts,
3538
+ // Zincirin BASI: kimlik gerektirmeyen uclar. Kullanicinin niyeti bu module
3539
+ // dustuyse ilk cagri bunlardan biridir; donen kimlikler sonraki adimlari besler.
3540
+ entryPoints: mod.entryPoints,
3209
3541
  endpoints: (mod.endpoints || []).map((e) => ({
3210
3542
  path: e.path,
3211
3543
  summary: e.summary,
@@ -3213,6 +3545,8 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3213
3545
  method: mm.method,
3214
3546
  safetyClass: mm.safetyClass,
3215
3547
  requiredPermission: mm.requiredPermission,
3548
+ // true ise once describe_endpoint ile onkosullari oku.
3549
+ hasPrerequisites: mm.hasPrerequisites,
3216
3550
  })),
3217
3551
  })),
3218
3552
  };
@@ -3221,14 +3555,14 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3221
3555
  {
3222
3556
  name: 'describe_endpoint',
3223
3557
  description:
3224
- 'Get full detail for one capability: input field types (fields), response shape ' +
3225
- '(responseSchema the data body inside the {success, data} envelope), safety class, ' +
3226
- 'required permission, and summary/scenario when available. Use the path + method from ' +
3558
+ 'Get full detail for one capability: input field types (fields), full HTTP Response ' +
3559
+ 'Contract v2 (status, transport, content type, envelope and data/body schema), safety class, ' +
3560
+ 'required permission, prerequisites (which id this call needs FIRST and which endpoint produces it), and summary/scenario when available. Use the path + method from ' +
3227
3561
  'list_capabilities. Returns only if the key is permitted to call it.',
3228
3562
  inputSchema: {
3229
3563
  type: 'object',
3230
3564
  additionalProperties: false,
3231
- required: ['path'],
3565
+ required: ['path', 'method'],
3232
3566
  properties: {
3233
3567
  path: {
3234
3568
  type: 'string',
@@ -3237,41 +3571,11 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
3237
3571
  method: {
3238
3572
  type: 'string',
3239
3573
  enum: ALLOWED_METHODS,
3240
- description: 'Optional HTTP method to disambiguate',
3574
+ description: 'HTTP method; required so multi-method paths are never selected ambiguously',
3241
3575
  },
3242
3576
  },
3243
3577
  },
3244
- handler: async (args) => {
3245
- const targetPath = String(args.path || '');
3246
- const targetMethod = args.method ? String(args.method).toUpperCase() : '';
3247
- const res = await systaFetch('GET', '/api-keys/me/capabilities');
3248
- if (!res.ok) {
3249
- return res;
3250
- }
3251
- const caps = unwrapCapabilities(res);
3252
- for (const module of caps.modules || []) {
3253
- for (const endpoint of module.endpoints || []) {
3254
- if (endpoint.path !== targetPath) {
3255
- continue;
3256
- }
3257
- const match = (endpoint.methods || []).find(
3258
- (mm) => !targetMethod || mm.method === targetMethod,
3259
- );
3260
- if (match) {
3261
- return {
3262
- module: module.module,
3263
- path: endpoint.path,
3264
- summary: endpoint.summary,
3265
- ...match,
3266
- };
3267
- }
3268
- }
3269
- }
3270
- return {
3271
- ok: false,
3272
- error: `Endpoint not found or not permitted for this key: ${targetMethod || 'ANY'} ${targetPath}`,
3273
- };
3274
- },
3578
+ handler: describeCapabilityEndpoint,
3275
3579
  },
3276
3580
 
3277
3581
  // ══════════════════════════════════════════════════════════════════════════
@@ -4180,7 +4484,7 @@ let allowedPermissionsPromise = null;
4180
4484
  // bir kez cekilir (memoized). Basarisizlik -> null (cagiran fail-open/closed'a karar verir).
4181
4485
  const fetchAllowedPermissions = async () => {
4182
4486
  try {
4183
- const res = await systaFetch('GET', '/api-keys/me/capabilities');
4487
+ const res = await systaFetch('GET', capabilityProjectionPath('compact'));
4184
4488
  if (!res || !res.ok) {
4185
4489
  return null;
4186
4490
  }
@@ -4447,6 +4751,7 @@ if (require.main === module) {
4447
4751
  }
4448
4752
 
4449
4753
  module.exports = {
4754
+ INSTRUCTIONS,
4450
4755
  TOOLS,
4451
4756
  TOOL_MAP,
4452
4757
  TOOL_REQUIRED_PERMISSION,