Tmavý režim
Kontrakt pro backend aplikace (MCP adapter v1)
Backend aplikace, který chce AI zpřístupnit vlastní data, implementuje dva endpointy pod libovolnou základní URL (tu admin zadá u instance jako URL backendu):
GET <base>/manifest → seznam nástrojů
POST <base>/call → provedení nástrojeOAuth, MCP protokol, kontrolu oprávnění a audit řeší user-api. Backend jen ověří JWT a znovu zkontroluje přístup k datům (defense in depth — nikdy nespoléhejte jen na gateway).
Autentizace volání
Každý požadavek nese Authorization: Bearer <JWT>:
| Claim | Hodnota |
|---|---|
iss | veřejný origin user-api, např. https://api.auth.amotion.cloud |
aud | atrea-mcp:<appCode> |
sub | e-mail uživatele (u /call), atrea-user-api (u /manifest) |
kind | mcp-call / mcp-manifest |
instance_id, tool, grant_id, client_id | kontext volání |
exp | 60 s od vydání |
Algoritmus RS256, klíče na <iss>/.well-known/jwks.json (hlavička kid).
ts
import { createRemoteJWKSet, jwtVerify } from 'jose'
const ISSUER = 'https://api.auth.amotion.cloud'
const jwks = createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`))
export async function verifyGateway(req, kind: 'mcp-call' | 'mcp-manifest') {
const token = req.headers.authorization?.replace(/^Bearer /, '')
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
audience: 'atrea-mcp:kgb',
algorithms: ['RS256'],
maxTokenAge: '60s',
})
if (payload.kind !== kind) throw new Error('wrong token kind')
return payload // payload.sub = e-mail uživatele
}GET /manifest
json
{
"version": 1,
"tools": [
{
"name": "list_projects",
"title": "Seznam projektů",
"description": "Vrátí projekty, ke kterým má uživatel přístup. Volitelně filtruje podle názvu.",
"inputSchema": { "type": "object", "properties": { "q": { "type": "string" } }, "additionalProperties": false },
"readOnly": true,
"permission": { "code": "project", "access": "view" }
},
{
"name": "rename_project",
"description": "Přejmenuje projekt.",
"inputSchema": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" } }, "required": ["id", "name"] },
"readOnly": false,
"destructive": false,
"permission": { "code": "project", "access": "edit" }
}
]
}Pravidla (porušení = nástroj je odmítnut a admin to vidí v panelu):
name:^[A-Za-z][A-Za-z0-9_-]{0,63}$, prefixatrea_je rezervovaný.descriptionpovinný — podle něj AI rozhoduje, kdy nástroj použít. Pište konkrétně.inputSchema(a volitelněoutputSchema) je JSON Schema stype: "object". Argumenty validuje gateway i před voláním backendu.readOnlypovinnětrue/false. Read-only konektor nástroje sfalsenikdy nevystaví.permissionpovinně:{ code, access: "view" | "edit" }(kód musí v aplikaci existovat), nebonull= kdokoli s přístupem k instanci.- Volitelně
destructive,idempotent,openWorld(MCP anotace pro klienta).
Manifest se cachuje 60 s; při výpadku backendu se 15 minut používá poslední platný.
POST /call
Požadavek:
json
{ "tool": "list_projects", "arguments": { "q": "Al" }, "context": { "appCode": "kgb", "instanceId": 3, "hostname": "kgb.amotion.cloud" } }Odpověď (HTTP 200):
json
{ "ok": true, "data": { "projects": [{ "id": 1, "name": "Alfa" }] } }
{ "ok": true, "text": "Projekt přejmenován." }
{ "ok": false, "error": { "message": "Projekt neexistuje." } }text se zobrazí modelu přímo; jinak se data serializuje do JSON. Zprávu v error.message uvidí uživatel i model — nevkládejte do ní interní detaily. Limit odpovědi 2 MB, timeout 30 s.
Doporučení
- Oprávnění na úrovni zdroje (projekt, jednotka) kontrolujte v backendu stejně jako u REST API — např.
POST /api/check/resourcesv user-api pro e-mail zesub. - Nástroje navrhujte úzké a pojmenované podle záměru (
list_units,get_unit_state), ne jako obecný proxy na REST API. - Zapisovací nástroje dělejte idempotentní, kde to jde, a vracejte stručné potvrzení.