Build workforce-api REST + MCP layers (Layer 3), full build in one pass

16 routes: Office (single key, full CRUD + employees sub-resource)
and Employee (single key, full CRUD + reports + customers
sub-resources). MCP layer built with the single-route pattern from
the start.

Only domain where customer role has zero access — verified against
ReferenceDataPermission.check(): read_only/product_manager/support
get reads, writes are admin-only, customer isn't in the allow-list
at all. All 9 MCP tool ACLs reflect this exactly.

Keycloak (local): workforce-api-audience + mcp-workforce-audience
client scopes, resource clients, broker/librechat attachments.
Deploy key and broker secret reused/mirrored, same pattern as every
domain since payments-api.
This commit is contained in:
Stéphane Tailland
2026-08-23 17:13:45 +02:00
parent 7430a88563
commit 2880e21ef1
28 changed files with 1609 additions and 11 deletions
+267
View File
@@ -0,0 +1,267 @@
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: ${NAME}-workforce-mcp-ai-mcp-proxy
namespace: ${NAMESPACE}
plugin: ai-mcp-proxy
config:
mode: conversion-listener
include_consumer_groups: true
tools:
- name: list-offices
description: Retrieves a paginated list of offices in the classic-models organization.
annotations:
title: List all offices
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
# Verified against ReferenceDataPermission.check() — read_only,
# product_manager, support pass for SAFE_METHODS; customer has NO
# access at all (not in the allow-list, unlike every other domain
# built this session). Not guessed — this domain is internal staff
# data, the backend enforces that explicitly.
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/offices/
- name: get-office
description: Retrieves a single office by office code.
annotations:
title: Get an office
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/offices/{officecode}/
parameters:
- name: officecode
in: path
required: true
description: The office's unique code.
schema: {}
- name: get-office-employees
description: Retrieves all employees working in a specific office.
annotations:
title: Get office employees
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/offices/{officecode}/employees/
parameters:
- name: officecode
in: path
required: true
description: The office's unique code.
schema: {}
- name: create-office
description: Creates a new office in the organization.
annotations:
title: Create an office
read_only_hint: false
idempotent_hint: false
destructive_hint: false
open_world_hint: false
# Writes are admin-only under ReferenceDataPermission (check()
# returns False for non-admin on any non-SAFE method).
acl:
allow:
- admin
method: POST
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/offices/
request_body:
required: true
content:
application/json:
schema:
type: object
properties:
officecode:
type: string
city:
type: string
phone:
type: string
addressline1:
type: string
addressline2:
type: string
state:
type: string
country:
type: string
postalcode:
type: string
territory:
type: string
required:
- officecode
- city
- phone
- addressline1
- country
- postalcode
- territory
- name: list-employees
description: Retrieves a paginated list of employees in the classic-models organization.
annotations:
title: List all employees
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/employees/
- name: get-employee
description: Retrieves a single employee by employee number.
annotations:
title: Get an employee
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/employees/{employeenumber}/
parameters:
- name: employeenumber
in: path
required: true
description: The employee's unique number.
schema: {}
- name: get-employee-reports
description: Retrieves all employees reporting to a specific manager.
annotations:
title: Get employee reports
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/employees/{employeenumber}/reports/
parameters:
- name: employeenumber
in: path
required: true
description: The manager's employee number.
schema: {}
- name: get-employee-customers
description: Retrieves all customers assigned to a specific sales representative.
annotations:
title: Get employee's customers
read_only_hint: true
idempotent_hint: true
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
- read_only
- product_manager
- support
method: GET
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/employees/{employeenumber}/customers/
parameters:
- name: employeenumber
in: path
required: true
description: The sales rep's employee number.
schema: {}
- name: create-employee
description: Adds a new employee to the organization.
annotations:
title: Create an employee
read_only_hint: false
idempotent_hint: false
destructive_hint: false
open_world_hint: false
acl:
allow:
- admin
method: POST
scheme: http
host: ${REST_HOSTNAME}
path: /workforce/v1/employees/
request_body:
required: true
content:
application/json:
schema:
type: object
properties:
employeenumber:
type: integer
lastname:
type: string
firstname:
type: string
extension:
type: string
email:
type: string
officecode:
type: string
reportsto:
type: integer
jobtitle:
type: string
required:
- employeenumber
- lastname
- firstname
- extension
- email
- officecode
- jobtitle