MCP Server
The Payroll Engine MCP Server exposes payroll data as typed tools that AI clients (Claude Desktop, GitHub Copilot, Cursor, etc.) can invoke directly using natural language. It communicates via stdio transport using the Model Context Protocol and is built on PayrollEngine.Mcp.Core and PayrollEngine.Mcp.Tools.
Prerequisites
- Payroll Engine Backend running
- .NET 10 SDK
- An MCP-compatible AI client
Read-Only by Design
The Payroll Engine MCP Server is a pure information and analysis tool. Write operations are not exposed — with two exceptions: get_employee_pay_preview and execute_payroll_report execute synchronous calculations that return results without persisting anything to the database.
This is a deliberate architectural decision. Payroll data is sensitive and consequential: a misplaced write operation could corrupt payrun results, trigger unintended retroactive chains, or leave silent mutations that are hard to detect. The risks of write access through an AI agent far outweigh any convenience gained.
Tasks that require data changes — such as case entries or tenant setup — are handled by purpose-built interfaces (WebApp, PayrollConsole, REST API) that provide the validation and confirmation workflows these operations need.
Access Control
The MCP Server controls access through two independent dimensions that answer different questions:
| Dimension | Controls | Applied |
|---|---|---|
| Isolation Level | Which records are returned | At runtime, per query |
| Permissions | Which tools are registered | At startup, once |
Isolation Level restricts data — a Tenant-isolated server cannot return records from another tenant regardless of which tools are active. Permissions restrict functionality — a tool that is not granted is invisible to the AI agent and does not appear in the tool list at all.
Isolation Level
Controls which records are returned at runtime. The isolation level is configured at startup and cannot be changed at runtime — ensuring that sensitive payroll data is always scoped correctly.
| Value | Description |
|---|---|
MultiTenant | Full access across all tenants (default) |
Tenant | All tool calls scoped to a single tenant |
Division | Scoped to a single division within a tenant. Requires TenantIdentifier and DivisionName. |
Employee | Self-service — single employee access. Requires TenantIdentifier and EmployeeIdentifier. |
Roles
Controls which tools are registered at startup. Each tool belongs to exactly one role. A tool whose role is not granted is invisible to the AI agent.
| Value | Domain |
|---|---|
HR |
Employee master data, case values, and audit trail |
Payroll |
Payroll execution, results, preview calculations, temporal case value queries, and lookup resolution |
Report |
Payroll report execution and result analysis |
System |
Tenant and user management |
Server — Always Available
The server tool is registered unconditionally — regardless of role permissions or isolation level.
| Tool | Description |
|---|---|
get_ |
Returns server version, active isolation level, configured scope, and role permissions. Use to verify which build is running and how it is configured. |
HR — Human Resources
Employee master data and organisational structure: who is employed, in which division, under what conditions, and how that data has changed over time. Includes the full case value history and the complete audit trail of all data mutations.
Organisation
| Tool | Description |
|---|---|
list_ |
List all divisions of a tenant |
get_ |
Get a division by name |
get_ |
Get a single custom attribute of a division |
list_ |
List employees with optional OData filter (e.g. lastName eq 'Müller') |
get_ |
Get an employee by identifier |
get_ |
Get a single custom attribute of an employee |
Case Values
Case values are the current and historical field values of an employee or company (e.g. salary, address, IBAN). The history contains all versions with their validity period.
| Tool | Description |
|---|---|
list_ |
Full case value history of an employee — all fields with start/end dates. Result includes employee context (identifier, first name, last name). |
list_ |
Company-level case values of a tenant — all company fields with start/end dates. |
Case Changes
Case changes are the audit trail of data mutations: each change records who made it, in which payroll, with what reason, and which field values were affected.
| Tool | Description |
|---|---|
list_ |
Audit trail of employee data mutations. Answers "who changed what and when" for a specific employee. Supports OData filter and top. Result includes employee context. |
list_ |
Audit trail of company data mutations. Supports OData filter and top. |
Payroll — Payroll Processing
Payroll execution and result analysis: payroll structure, payruns, payrun jobs, result values, preview calculations, temporal case value queries, and lookup resolution. A Payroll Specialist who needs to look up employees requires HR: Read in addition.
Structure
| Tool | Description |
|---|---|
list_ |
List all payrolls of a tenant |
get_ |
Get a payroll by name |
list_ |
List all payruns of a tenant |
list_ |
List all payrun jobs, ordered by creation date descending. Includes period, status, employee count, and job timing. Result contains a divisions lookup (id → name) alongside the payrunJobs array. |
list_ |
Effective wage types of a payroll, merged across all regulation layers. |
get_ |
Resolved lookup value for a given key or range value, merged across all regulation layers. Use lookupKey for exact-key lookups and rangeValue for progressive lookups (e.g. income bracket). |
Results
Payroll results reflect what was calculated during payrun execution. Two complementary views are available:
| Tool | Description |
|---|---|
list_ |
Flat list of all result values (wage types and collectors). Fully denormalized — each row includes employeeIdentifier, payrollName, periodName, payrunName, and jobName. Supports optional filter by employee or payroll, plus OData filter. Use for cross-employee or cross-period analysis. |
get_ |
All wage type results, collector results, and payrun results for one employee and one period in a single response. Use for a complete per-employee per-period overview. Result includes employee context and period boundaries. |
Preview
| Tool | Description |
|---|---|
get_ |
Preview the payroll calculation for a single employee without persisting results. Returns wage type results, collector results, and payrun results. Requires McpServer:PreviewUserIdentifier to be configured. |
Temporal Case Values
get_case_time_values queries case values as they were valid at a specific point in time, with three temporal perspectives:
| Tool | Description |
|---|---|
get_ |
Case values valid at a specific point in time. Supports Employee, Company, and Global case types. When scoped to a single employee via employeeIdentifier, result includes employee context. |
Report — Report Execution
Payroll report execution and result analysis. Reports are resolved across all regulation layers of the payroll and return one or more named result tables that the AI can analyse and summarise. Requires McpServer:PreviewUserIdentifier to be configured.
| Tool | Description |
|---|---|
execute_ |
Execute a payroll report and return its result data set. The report is resolved across all regulation layers of the payroll. Use the parameters dictionary to pass report-specific input values (e.g. period, employee filter). |
System — Administration
Tenant and user queries for cross-tenant administration and user management. In Tenant-isolated deployments the tenant context is fixed at startup — System tools are typically only needed for cross-tenant administration.
| Tool | Description |
|---|---|
list_ |
List all tenants |
get_ |
Get a tenant by identifier |
get_ |
Get a single custom attribute of a tenant |
list_ |
List all users of a tenant |
get_ |
Get a user by identifier |
get_ |
Get a single custom attribute of a user |
Permissions
Each role is independently enabled or disabled per deployment. The default when the Permissions section is absent is Read for all roles.
| Value | Description |
|---|---|
None |
Role tools are not registered — invisible to the AI agent |
Read |
Query tools registered (default) |
Role Isolation Level
Not every role is applicable at every isolation level. The table shows which permission values can be assigned per role and isolation level. Roles marked ☐ have no meaningful scope at that level — their tools operate on data that is not accessible within the isolation boundary.
✅ = permission can be assigned (None / Read)
☐ = not applicable at this isolation level
| Role | MultiTenant | Tenant | Division | Employee |
|---|---|---|---|---|
| HR | ✅ | ✅ | ✅ | ✅ |
| Payroll | ✅ | ✅ | ✅ | ☐ |
| Report | ✅ | ✅ | ☐ | ☐ |
| System | ✅ | ✅ | ☐ | ☐ |
Persona Examples
✅ = Read ☐ = None
| Persona | HR | Payroll | Report | System |
|---|---|---|---|---|
| HR Manager | ✅ | ☐ | ☐ | ☐ |
| Payroll Specialist | ✅ | ✅ | ☐ | ☐ |
| HR Business Partner | ✅ | ✅ | ☐ | ☐ |
| Controller / Analyst | ✅ | ✅ | ✅ | ☐ |
| Report Analyst | ☐ | ☐ | ✅ | ☐ |
| System Administrator | ☐ | ☐ | ☐ | ✅ |
| Developer | ✅ | ✅ | ✅ | ✅ |
Case Value Temporal Perspectives
The get_case_time_values tool controls which data is returned through two independent date parameters:
| Parameter | Controls |
|---|---|
valueDate |
What is valid — selects values whose Start <= valueDate < End |
evaluationDate |
When the query is evaluated — filters out values created after this date |
The combination of these two parameters defines the temporal perspective of the query.
valueDate — "What was valid on this day?"
Selects which value was active on a given date. A case field like Salary may have multiple entries with different validity periods — valueDate picks the one whose range covers the requested date.
evaluationDate — "What did the system know at this point?"
Filters out entries whose Created timestamp is later than the evaluation date. This is relevant for retroactive corrections: a salary correction entered today but backdated to January 1st has Start = 2025-01-01 but Created = today.
- With
evaluationDate = today: the correction is visible. - With
evaluationDate = 2025-01-01: the correction is not visible (it was entered later).
Three Perspectives
| Perspective | Value Date | Evaluation Date | forecast | Typical question |
|---|---|---|---|---|
| Historical | target date | = valueDate | — | "What was true on Jan 1, as of Jan 1?" |
| Current knowledge | target date | today (default) | — | "What do we now know about Jan 1?" |
| Forecast | future date | = valueDate | name | "What will be true on Jul 1 per the plan?" |
Historical — evaluationDate = valueDate: retroactive corrections entered after the target date are excluded. Use for audit and compliance.
Current knowledge — evaluationDate omitted or set to today: all corrections entered since are included. Use for reporting and data quality checks.
Forecast — evaluationDate = valueDate with a forecast name: planned future values are included. evaluationDate must match valueDate (not today) to ensure future-dated forecast entries are visible. Use for budget planning and salary projections.
Parameters
| Parameter | Type | Description |
|---|---|---|
valueDate |
string (ISO 8601) |
The point in time for value validity. Default: today. |
evaluationDate |
string (ISO 8601) |
The knowledge cutoff date. Default: today. |
forecast |
string |
Forecast name. Omit for real values only. |
employeeIdentifier |
string |
Employee identifier. Omit for all employees (tenant-wide). |
caseFieldNames |
string (comma-separated) |
Filter by specific fields, e.g. "Salary,City,IBAN". Omit for all fields. |
See Temporal Perspectives for a detailed explanation of the two time axes, including the retro 2×2 matrix and forecast entry expiry scenarios.
Example Prompts
List all tenants
Show me the employees of StartTenant
What case values does mario.nunez@foo.com have in StartTenant?
What changed in the employee data of mario.nunez@foo.com in January 2026?
List the lookup values of VatRates in SwissRegulation of CH-Monthly
What wage types are effective in the CH-Monthly payroll of StartTenant?
What was mario.nunez@foo.com's salary on 2025-01-01?
Show me all payroll results for Müller in March 2026
What would the payroll look like for Müller in April 2026?
Run the MonthlyPayslip report for the CH-Monthly payroll
Configuration
Backend connection settings in Server/appsettings.json:
{
"ApiSettings": {
"BaseUrl": "https://localhost",
"Port": 443
}
}
Sensitive settings (API key) go in apisettings.json — excluded from source control:
{
"ApiSettings": {
"ApiKey": "your-api-key"
}
}
Isolation level and role permissions in appsettings.json:
{
"McpServer": {
"IsolationLevel": "Tenant",
"TenantIdentifier": "acme-corp",
"Permissions": {
"HR": "Read",
"Payroll": "Read",
"Report": "Read",
"System": "None"
}
}
}
For Division isolation:
{
"McpServer": {
"IsolationLevel": "Division",
"TenantIdentifier": "acme-corp",
"DivisionName": "sales"
}
}
For Employee isolation:
{
"McpServer": {
"IsolationLevel": "Employee",
"TenantIdentifier": "acme-corp",
"EmployeeIdentifier": "mario.nunez@acme.com"
}
}
For payrun preview and report execution (get_employee_pay_preview, execute_payroll_report), configure a service account user that exists in the target tenant:
{
"McpServer": {
"PreviewUserIdentifier": "mcp-service@acme.com"
}
}
Settings can also be provided as environment variables using the __ separator:
McpServer__IsolationLevel=Tenant
McpServer__TenantIdentifier=acme-corp
McpServer__DivisionName=sales
McpServer__EmployeeIdentifier=mario.nunez@acme.com
McpServer__PreviewUserIdentifier=mcp-service@acme.com
McpServer__Permissions__HR=Read
McpServer__Permissions__Payroll=Read
McpServer__Permissions__Report=Read
McpServer__Permissions__System=None
ApiSettings__BaseUrl=https://your-backend
ApiSettings__Port=443
AllowInsecureSsl=true
Client Setup
Claude Desktop
Add to %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"payroll-engine": {
"command": "dotnet",
"args": [
"run",
"--project",
"path/to/Server/PayrollEngine.Mcp.Server.csproj",
"--no-launch-profile",
"--no-build"
],
"env": {
"DOTNET_ENVIRONMENT": "Development",
"ApiSettings__BaseUrl": "https://localhost",
"ApiSettings__Port": "443",
"AllowInsecureSsl": "true"
}
}
}
}
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json in the project root:
{
"mcpServers": {
"payroll-engine": {
"command": "dotnet",
"args": [
"run",
"--project",
"path/to/Server/PayrollEngine.Mcp.Server.csproj",
"--no-launch-profile",
"--no-build"
],
"env": {
"DOTNET_ENVIRONMENT": "Development",
"ApiSettings__BaseUrl": "https://localhost",
"ApiSettings__Port": "443",
"AllowInsecureSsl": "true"
}
}
}
}
Docker
docker run --rm -i \
-e ApiSettings__BaseUrl=https://your-backend \
-e ApiSettings__Port=443 \
ghcr.io/payroll-engine/payrollengine.mcp.server
See Also
- MCP Server Repository — source code and releases
- MCP Server API Reference — generated API documentation (available once the repository is public)
- Backend — running the Payroll Engine Backend
- Client SDK — .NET SDK for deeper automation
- LLM-Assisted Development — generating regulations with AI
- DevOps — CI/CD and deployment patterns