Stéphane Tailland 8db2bb6e39 refactor(mcp): generate the tool definitions from the spec
Produced by k8s-platform/tools/generate-mcp-tools.py: deck file openapi2mcp
does the conversion, and the wrapper re-injects the five things Kong's
generator cannot emit — per-tool ACLs, the MCP behavioural annotations, the
response schemas the plugin uses for structuredOutput, the real upstream host,
and JSON-only request bodies. Each is a documented gap; see
k8s-platform/MCP-TOOL-GENERATION.md.

tools-policy.yaml carries the ACLs and doubles as the allow-list: an operation
becomes a tool only if the policy names it. Kong would otherwise expose every
operation in the spec, DELETE included, and can express no authorization at all
from a bearer-token spec — silently, exit 0, no warning.

Same tools as before, same ACLs. What changed: query parameters are now
declared and callable, each tool carries its response schema so calls return
structured content instead of raw JSON, and request bodies are no longer
tripled across three identical content types.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:48:45 +02:00

workforce-api

Kong routing/policy layer for the workforce slice of classic-models-api (see /Users/stephanetailland/git/classic-models-api), split out per the DDD decomposition in ../kong-konnect-deployment-strategy.md. The backend stays a single monolith — only Kong does the domain split. Structure mirrors ../sales-api/../payments-api — single MCP route from the start, not ../catalog-api's original two-route pattern (see ../STATUS.md section 18).

Two resources, both with a REST surface and MCP tools:

  • Office (single key officecode) — full CRUD + an employees sub-resource.
  • Employee (single key employeenumber) — full CRUD + reports (direct reports, self-referential FK) and customers (assigned sales rep's customers) sub-resources.
Route Method Backend path
/workforce/v1/offices/ GET, POST /classic-models/api/v1/offices/
/workforce/v1/offices/{officecode}/ GET, PUT, PATCH, DELETE /classic-models/api/v1/offices/{officecode}/
/workforce/v1/offices/{officecode}/employees/ GET /classic-models/api/v1/offices/{officecode}/employees/
/workforce/v1/employees/ GET, POST /classic-models/api/v1/employees/
/workforce/v1/employees/{employeenumber}/ GET, PUT, PATCH, DELETE /classic-models/api/v1/employees/{employeenumber}/
/workforce/v1/employees/{employeenumber}/reports/ GET /classic-models/api/v1/employees/{employeenumber}/reports/
/workforce/v1/employees/{employeenumber}/customers/ GET /classic-models/api/v1/employees/{employeenumber}/customers/
/workforce-mcp GET, POST, DELETE MCP streamable-http

Auth & MCP tool ACLs — the one domain where customer gets zero access

Both viewsets use ReferenceDataPermission (authentication/permissions.py): read_only/product_manager/support get read access, writes are admin-only, and — unlike every other domain built this session — customer is not in the allow-list at all, not even for reads. Verified directly against the permission class, not assumed: this is genuinely internal staff/organizational data, and the backend enforces that explicitly. All 7 read tools (list-offices, get-office, get-office-employees, list-employees, get-employee, get-employee-reports, get-employee-customers) have ACL [admin, read_only, product_manager, support] — no customer. Both write tools (create-office, create-employee) are admin only.

openid-connect on both surfaces (workforce-api REST audience, mcp-workforce MCP audience), RFC 8693 exchange chain MCP → workforce-api → classic-models-api. IdP: local Keycloak.

Structure

Same dev/ + kong/rest/ + kong/mcp/ layout as sales-api. Deploy key and broker secret reused/mirrored from catalog-api and the live broker client, same pattern as every domain since payments-api.

S
Description
No description provided
Readme 69 KiB