kodexa-api exposes its workflow surface — activities, tasks, and task-groups, plus assignment, status, and membership actions — as a Model Context Protocol (MCP) connector. You add the endpoint to claude.ai as a remote OAuth connector, and Claude can then query and manage your workflow in natural language, acting as you.
The connector is served by the Kodexa platform itself over HTTP — there is nothing to install on your machine. Every call is authenticated and every result is scoped to what the calling user is permitted to see, exactly like the REST API.
What The Connector Exposes
The MCP server registers as kodexa-workflow and presents 19 tools:
- Discovery — resolve who you are and the organizations, projects, teams, and members you can act on.
- Workflow queries — list and read activities, tasks, task-groups, and task statuses (read-only).
- Workflow actions — assign and unassign tasks and task-groups, move tasks between statuses, and add or remove tasks from a group.
Reads reuse the platform’s registry handlers; writes replay through the existing REST handlers. As a result access control (FGAC), optimistic locking, task-group cascades, audit, and domain events are identical to the REST surface — no logic is duplicated.
Workflow-model caveats the tools enforce:
- Activities are read-only over MCP. You can list and inspect them, but not mutate them.
- A grouped task’s assignee is managed via its group.
assign_task / unassign_task targeting the user (assignee) fail on a task that belongs to a task-group — assign the group with assign_task_group instead, and the assignment cascades to every member task. (A team-only assignment through assign_task is not blocked for grouped tasks, since a group owns the assignee, not the team.)
- Access is FGAC-scoped to the calling user. Claude sees and changes only what you could see and change yourself.
Enabling The Connector
The connector is off by default. A platform administrator enables it in the platform configuration:
| Setting | Purpose |
|---|
mcp.enabled | Turns the connector on. When false, the /mcp route is not mounted. |
mcp.publicUrl | The canonical, publicly reachable URL of the MCP endpoint. This value must equal the Auth0 API identifier (audience) for the OAuth flow, and it anchors the protected-resource metadata document. |
For the claude.ai OAuth flow, api.security.auth0.domain must also be set. On startup the platform builds an Auth0 token validator bound to mcp.publicUrl as the audience.
If mcp.enabled is set but mcp.publicUrl is empty, the connector is disabled. If the Auth0 domain is not configured (or the validator fails to initialize), the connector still works with the X-API-Key fallback but does not advertise the claude.ai OAuth flow.
The connector is mounted at <basePath>/mcp over the Streamable HTTP transport (stateless — each request is a self-contained, independently authenticated JSON-RPC message).
Adding It To claude.ai
Add the MCP endpoint (https://<your-mcp-publicUrl>/mcp) as a remote connector in claude.ai. The OAuth handshake is automatic:
- claude.ai calls the endpoint with no token and receives an RFC 9728
401 with a WWW-Authenticate: Bearer resource_metadata="…" header.
- It fetches
/.well-known/oauth-protected-resource from the endpoint’s origin, which points at your Auth0 authorization server.
- It performs Dynamic Client Registration (DCR) and an OAuth authorization-code + PKCE flow against Auth0, obtaining an access token whose audience matches
mcp.publicUrl.
- Every subsequent MCP call carries that bearer token, which the platform validates against Auth0’s JWKS (signature, issuer, expiry, audience) before landing an authenticated user in context.
The protected-resource metadata advertises the openid, profile, email, and offline_access scopes.
Programmatic And Local Clients
Clients that cannot run the OAuth flow can authenticate with an API key by sending it in the X-API-Key header instead of a bearer token. This is the fallback path used for local development and programmatic access.
All list tools share a common set of optional parameters: filter (an advanced SpringFilter expression, ANDed with the other filters), query (free-text search), sort (e.g. created_on:desc), page (1-indexed, default 1), and pageSize (default 20, max 1000).
Discovery
| Tool | Purpose | Key arguments |
|---|
whoami | Return the authenticated user’s identity (id, email, roles, groups, job title). Call this first. | — |
list_organizations | Organizations you can access. | list params |
list_projects | Projects, optionally scoped to an organization. | organizationId |
list_teams | Teams in an organization (assignment targets). | organizationId |
list_organization_members | Members of an organization (assignment targets). | organizationId (required) |
Workflow Queries (read-only)
| Tool | Purpose | Key arguments |
|---|
list_activities | Activities (runtime instances of activity plans). | projectId, lifecycleState (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED, REPLANNED) |
get_activity | A single activity, including its materialized steps. | id (required) |
list_tasks | Tasks, with rich filtering. | projectId, assigneeId, unassigned, statusType (OPEN, IN_PROGRESS, DONE, BLOCKED, PENDING), taskGroupId |
get_task | A single task. | id (required) |
list_task_groups | Task-groups (batch tasks for shared assignment and lifecycle). | projectId, teamId, assigneeId, statusType |
get_task_group | A single task-group. | id (required) |
list_task_statuses | Task statuses for an organization (optionally bound to a project). Use a status slug with update_task_status. | organizationId, projectId |
Workflow Actions (write)
| Tool | Purpose | Key arguments |
|---|
assign_task | Assign a task to a user and/or team. A user assignment fails if the task belongs to a task-group (assign the group instead) or the task is locked; a team-only assignment is allowed on grouped tasks. | taskId (required), userId, teamId, changeSequence |
unassign_task | Clear a task’s user and/or team assignment. Clearing the user fails if the task belongs to a task-group. | taskId (required), target (user, team, or both; default user) |
assign_task_group | Assign a task-group to a user; cascades to every member task. Fails if any member is locked. | taskGroupId (required), userId (required), changeSequence |
unassign_task_group | Clear a task-group’s user assignment, cascading the clear to all members. | taskGroupId (required) |
update_task_status | Move a task to a status by slug. Applies the status’s lock behavior and any group auto-complete/reopen. | taskId (required), status (slug, required), changeSequence, lockTask, lockDocumentFamily |
add_tasks_to_group | Add tasks to a group; the group’s assignee (if set) cascades to the new members. | taskGroupId (required), taskIds (required) |
remove_task_from_group | Remove a single task from a group, ungrouping it. | taskGroupId (required), taskId (required) |
Optimistic Concurrency
Tasks carry a changeSequence. The write tools that accept a changeSequence argument (assign_task, assign_task_group, update_task_status) use it for optimistic-concurrency safety: pass the sequence you last read, and the write fails cleanly if the task changed underneath you. It is optional — omit it to skip the check.
A Typical Session
A natural place to start is discovery, then narrow to the work you care about, then act:
Who am I, and which projects can I see?
Show me the unassigned tasks in the Invoices project that are still OPEN.
Assign the "Review BoL #1428" task to me.
Claude calls whoami and list_projects, then list_tasks with projectId, unassigned: true, and statusType: OPEN, then resolves your user id via list_organization_members and calls assign_task. Because everything runs through the platform’s normal access-control path, it can only ever do what you are permitted to do.