Skip to content

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ástroje

OAuth, 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>:

ClaimHodnota
issveřejný origin user-api, např. https://api.auth.amotion.cloud
audatrea-mcp:<appCode>
sube-mail uživatele (u /call), atrea-user-api (u /manifest)
kindmcp-call / mcp-manifest
instance_id, tool, grant_id, client_idkontext volání
exp60 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}$, prefix atrea_ je rezervovaný.
  • description povinný — podle něj AI rozhoduje, kdy nástroj použít. Pište konkrétně.
  • inputSchema (a volitelně outputSchema) je JSON Schema s type: "object". Argumenty validuje gateway i před voláním backendu.
  • readOnly povinně true/false. Read-only konektor nástroje s false nikdy nevystaví.
  • permission povinně: { code, access: "view" | "edit" } (kód musí v aplikaci existovat), nebo null = 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/resources v user-api pro e-mail ze sub.
  • 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í.

Atrea User API — interní dokumentace