systa-mcp 1.4.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.
- package/README.md +1 -1
- package/SKILL.md +26 -2
- package/package.json +1 -1
- package/server.js +343 -50
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ 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
|
|
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
|
package/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 1.
|
|
2
|
+
version: 1.5.0
|
|
3
3
|
name: systa
|
|
4
4
|
description: |
|
|
5
5
|
Drive SysTa (Talep Takip Sistemi / SysTa) — a
|
|
@@ -169,9 +169,33 @@ veya `describe_endpoint` ile gövdeyi netleştir.
|
|
|
169
169
|
You don't memorize 600+ endpoints. Discover on demand.
|
|
170
170
|
|
|
171
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.
|
|
172
|
-
- **`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 (
|
|
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.
|
|
173
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`.
|
|
174
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
|
+
|
|
175
199
|
## Çekirdek iş akışları (core workflows)
|
|
176
200
|
|
|
177
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "systa-mcp",
|
|
3
|
-
"version": "1.
|
|
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)`
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
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).',
|
|
@@ -407,6 +424,292 @@ const systaUploadMultipart = async (apiPath, method, filePaths) => {
|
|
|
407
424
|
const unwrapCapabilities = (res) =>
|
|
408
425
|
res && res.data && res.data.data ? res.data.data : res && res.data;
|
|
409
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
|
+
|
|
410
713
|
/**
|
|
411
714
|
* Compact view of the capability catalog — drops field_schema/detail/scenario so
|
|
412
715
|
* the per-turn payload stays small (lazy: use describe_endpoint for full detail).
|
|
@@ -444,7 +747,8 @@ const shapeCompact = (caps) => ({
|
|
|
444
747
|
method: mm.method,
|
|
445
748
|
safetyClass: mm.safetyClass,
|
|
446
749
|
requiredPermission: mm.requiredPermission,
|
|
447
|
-
hasFields: Boolean(mm.fields),
|
|
750
|
+
hasFields: Boolean(mm.hasFields || mm.fields),
|
|
751
|
+
hasResponseContract: Boolean(mm.hasResponseContract || mm.responseSchema),
|
|
448
752
|
})),
|
|
449
753
|
})),
|
|
450
754
|
})),
|
|
@@ -468,11 +772,16 @@ const shapeSummary = (caps) => ({
|
|
|
468
772
|
moduleCount: caps.moduleCount,
|
|
469
773
|
endpointCount: caps.endpointCount,
|
|
470
774
|
hint: 'Summary view — endpoints omitted. Call list_capabilities({module:"<name>"}) or describe_module to see a module\'s endpoints.',
|
|
471
|
-
modules: (caps.modules || []).map((m) =>
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
+
}),
|
|
476
785
|
});
|
|
477
786
|
|
|
478
787
|
const shapeOneModule = (caps, target) => {
|
|
@@ -3169,7 +3478,13 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3169
3478
|
},
|
|
3170
3479
|
},
|
|
3171
3480
|
handler: async (args) => {
|
|
3172
|
-
|
|
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);
|
|
3173
3488
|
if (!res.ok) {
|
|
3174
3489
|
return res;
|
|
3175
3490
|
}
|
|
@@ -3187,7 +3502,7 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3187
3502
|
name: 'describe_module',
|
|
3188
3503
|
description:
|
|
3189
3504
|
'Orient on ONE SysTa module: returns its purpose (description, TR/EN), key concepts, ' +
|
|
3190
|
-
'
|
|
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 ' +
|
|
3191
3506
|
'module name from list_capabilities (e.g. "request", "kanban", "plan", "sla"). Helps map a ' +
|
|
3192
3507
|
"user's natural-language intent to the right module before describe_endpoint.",
|
|
3193
3508
|
inputSchema: {
|
|
@@ -3199,7 +3514,10 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3199
3514
|
},
|
|
3200
3515
|
},
|
|
3201
3516
|
handler: async (args) => {
|
|
3202
|
-
const res = await systaFetch(
|
|
3517
|
+
const res = await systaFetch(
|
|
3518
|
+
'GET',
|
|
3519
|
+
capabilityProjectionPath('module', { module: args.module }),
|
|
3520
|
+
);
|
|
3203
3521
|
if (!res.ok) {
|
|
3204
3522
|
return res;
|
|
3205
3523
|
}
|
|
@@ -3217,6 +3535,9 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3217
3535
|
module: mod.module,
|
|
3218
3536
|
description: mod.description,
|
|
3219
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,
|
|
3220
3541
|
endpoints: (mod.endpoints || []).map((e) => ({
|
|
3221
3542
|
path: e.path,
|
|
3222
3543
|
summary: e.summary,
|
|
@@ -3224,6 +3545,8 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3224
3545
|
method: mm.method,
|
|
3225
3546
|
safetyClass: mm.safetyClass,
|
|
3226
3547
|
requiredPermission: mm.requiredPermission,
|
|
3548
|
+
// true ise once describe_endpoint ile onkosullari oku.
|
|
3549
|
+
hasPrerequisites: mm.hasPrerequisites,
|
|
3227
3550
|
})),
|
|
3228
3551
|
})),
|
|
3229
3552
|
};
|
|
@@ -3232,14 +3555,14 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3232
3555
|
{
|
|
3233
3556
|
name: 'describe_endpoint',
|
|
3234
3557
|
description:
|
|
3235
|
-
'Get full detail for one capability: input field types (fields),
|
|
3236
|
-
'
|
|
3237
|
-
'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 ' +
|
|
3238
3561
|
'list_capabilities. Returns only if the key is permitted to call it.',
|
|
3239
3562
|
inputSchema: {
|
|
3240
3563
|
type: 'object',
|
|
3241
3564
|
additionalProperties: false,
|
|
3242
|
-
required: ['path'],
|
|
3565
|
+
required: ['path', 'method'],
|
|
3243
3566
|
properties: {
|
|
3244
3567
|
path: {
|
|
3245
3568
|
type: 'string',
|
|
@@ -3248,41 +3571,11 @@ The number is the request number for "#", and the card's GLOBAL id for "##" (NOT
|
|
|
3248
3571
|
method: {
|
|
3249
3572
|
type: 'string',
|
|
3250
3573
|
enum: ALLOWED_METHODS,
|
|
3251
|
-
description: '
|
|
3574
|
+
description: 'HTTP method; required so multi-method paths are never selected ambiguously',
|
|
3252
3575
|
},
|
|
3253
3576
|
},
|
|
3254
3577
|
},
|
|
3255
|
-
handler:
|
|
3256
|
-
const targetPath = String(args.path || '');
|
|
3257
|
-
const targetMethod = args.method ? String(args.method).toUpperCase() : '';
|
|
3258
|
-
const res = await systaFetch('GET', '/api-keys/me/capabilities');
|
|
3259
|
-
if (!res.ok) {
|
|
3260
|
-
return res;
|
|
3261
|
-
}
|
|
3262
|
-
const caps = unwrapCapabilities(res);
|
|
3263
|
-
for (const module of caps.modules || []) {
|
|
3264
|
-
for (const endpoint of module.endpoints || []) {
|
|
3265
|
-
if (endpoint.path !== targetPath) {
|
|
3266
|
-
continue;
|
|
3267
|
-
}
|
|
3268
|
-
const match = (endpoint.methods || []).find(
|
|
3269
|
-
(mm) => !targetMethod || mm.method === targetMethod,
|
|
3270
|
-
);
|
|
3271
|
-
if (match) {
|
|
3272
|
-
return {
|
|
3273
|
-
module: module.module,
|
|
3274
|
-
path: endpoint.path,
|
|
3275
|
-
summary: endpoint.summary,
|
|
3276
|
-
...match,
|
|
3277
|
-
};
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
}
|
|
3281
|
-
return {
|
|
3282
|
-
ok: false,
|
|
3283
|
-
error: `Endpoint not found or not permitted for this key: ${targetMethod || 'ANY'} ${targetPath}`,
|
|
3284
|
-
};
|
|
3285
|
-
},
|
|
3578
|
+
handler: describeCapabilityEndpoint,
|
|
3286
3579
|
},
|
|
3287
3580
|
|
|
3288
3581
|
// ══════════════════════════════════════════════════════════════════════════
|
|
@@ -4191,7 +4484,7 @@ let allowedPermissionsPromise = null;
|
|
|
4191
4484
|
// bir kez cekilir (memoized). Basarisizlik -> null (cagiran fail-open/closed'a karar verir).
|
|
4192
4485
|
const fetchAllowedPermissions = async () => {
|
|
4193
4486
|
try {
|
|
4194
|
-
const res = await systaFetch('GET', '
|
|
4487
|
+
const res = await systaFetch('GET', capabilityProjectionPath('compact'));
|
|
4195
4488
|
if (!res || !res.ok) {
|
|
4196
4489
|
return null;
|
|
4197
4490
|
}
|