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:
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongPluginBinding
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-request-binding
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
pluginRef:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-request
|
||||
targets:
|
||||
serviceRef:
|
||||
group: configuration.konghq.com
|
||||
kind: KongService
|
||||
name: ${NAME}-workforce-mcp
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongPluginBinding
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-response-binding
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
pluginRef:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-response
|
||||
targets:
|
||||
serviceRef:
|
||||
group: configuration.konghq.com
|
||||
kind: KongService
|
||||
name: ${NAME}-workforce-mcp
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: configuration.konghq.com/v1
|
||||
kind: KongPlugin
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-request
|
||||
namespace: ${NAMESPACE}
|
||||
plugin: pre-function
|
||||
config:
|
||||
access:
|
||||
- "kong.ctx.shared.audit_request_body = kong.request.get_raw_body()"
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1
|
||||
kind: KongPlugin
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-capture-response
|
||||
namespace: ${NAMESPACE}
|
||||
plugin: post-function
|
||||
config:
|
||||
body_filter:
|
||||
- |
|
||||
local chunk = ngx.arg[1]
|
||||
if chunk then
|
||||
kong.ctx.shared.audit_response_body = (kong.ctx.shared.audit_response_body or "") .. chunk
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: configuration.konghq.com/v1
|
||||
kind: KongPlugin
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-log
|
||||
namespace: ${NAMESPACE}
|
||||
plugin: http-log
|
||||
config:
|
||||
http_endpoint: ${AUDIT_LOG_ENDPOINT}
|
||||
method: POST
|
||||
content_type: application/json
|
||||
custom_fields_by_lua:
|
||||
request_body: "return kong.ctx.shared.audit_request_body"
|
||||
response_body: "return kong.ctx.shared.audit_response_body"
|
||||
@@ -0,0 +1,59 @@
|
||||
apiVersion: configuration.konghq.com/v1beta1
|
||||
kind: KongConsumerGroup
|
||||
metadata:
|
||||
name: ${NAME}-admin
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: admin
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1beta1
|
||||
kind: KongConsumerGroup
|
||||
metadata:
|
||||
name: ${NAME}-read-only
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: read_only
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1beta1
|
||||
kind: KongConsumerGroup
|
||||
metadata:
|
||||
name: ${NAME}-product-manager
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: product_manager
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1beta1
|
||||
kind: KongConsumerGroup
|
||||
metadata:
|
||||
name: ${NAME}-customer
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: customer
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1beta1
|
||||
kind: KongConsumerGroup
|
||||
metadata:
|
||||
name: ${NAME}-support
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: support
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: configuration.konghq.com/v1
|
||||
kind: KongPlugin
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-openid-connect
|
||||
namespace: ${NAMESPACE}
|
||||
plugin: openid-connect
|
||||
config:
|
||||
issuer: ${OIDC_ISSUER}
|
||||
audience_required: ${API_AUDIENCE}
|
||||
auth_methods:
|
||||
- bearer
|
||||
display_errors: false
|
||||
ssl_verify: true
|
||||
consumer_groups_claim:
|
||||
- realm_access
|
||||
- roles
|
||||
client_id:
|
||||
- ${TOKEN_EXCHANGE_CLIENT_ID}
|
||||
client_secret:
|
||||
- ${TOKEN_EXCHANGE_CLIENT_SECRET}
|
||||
token_exchange:
|
||||
subject_token_issuers:
|
||||
- issuer: ${OIDC_ISSUER}
|
||||
conditions: {}
|
||||
request:
|
||||
audience: ${API_AUDIENCE}
|
||||
scopes: []
|
||||
@@ -0,0 +1,53 @@
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongPluginBinding
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-openid-connect-binding
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
pluginRef:
|
||||
name: ${NAME}-workforce-mcp-openid-connect
|
||||
targets:
|
||||
serviceRef:
|
||||
group: configuration.konghq.com
|
||||
kind: KongService
|
||||
name: ${NAME}-workforce-mcp
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongPluginBinding
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-ai-mcp-proxy-binding
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
pluginRef:
|
||||
name: ${NAME}-workforce-mcp-ai-mcp-proxy
|
||||
targets:
|
||||
routeRef:
|
||||
group: configuration.konghq.com
|
||||
kind: KongRoute
|
||||
name: ${NAME}-workforce-mcp
|
||||
---
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongPluginBinding
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp-audit-log-binding
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
pluginRef:
|
||||
name: ${NAME}-workforce-mcp-audit-log
|
||||
targets:
|
||||
serviceRef:
|
||||
group: configuration.konghq.com
|
||||
kind: KongService
|
||||
name: ${NAME}-workforce-mcp
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongRoute
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
serviceRef:
|
||||
type: namespacedRef
|
||||
namespacedRef:
|
||||
name: ${NAME}-workforce-mcp
|
||||
name: workforce-mcp
|
||||
hosts:
|
||||
- ${REST_HOSTNAME}
|
||||
methods:
|
||||
- GET
|
||||
- POST
|
||||
- DELETE
|
||||
paths:
|
||||
- /workforce-mcp
|
||||
strip_path: false
|
||||
tags:
|
||||
- workforce
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: configuration.konghq.com/v1alpha1
|
||||
kind: KongService
|
||||
metadata:
|
||||
name: ${NAME}-workforce-mcp
|
||||
namespace: ${NAMESPACE}
|
||||
spec:
|
||||
controlPlaneRef:
|
||||
type: konnectNamespacedRef
|
||||
konnectNamespacedRef:
|
||||
name: ${CONTROL_PLANE_REF_NAME}
|
||||
name: workforce-mcp
|
||||
host: ${REST_HOSTNAME}
|
||||
port: 80
|
||||
protocol: http
|
||||
path: /
|
||||
tags:
|
||||
- workforce
|
||||
Reference in New Issue
Block a user