npm package: @pitra190/unit4-timesheet-mcp
Description: MCP server for syncing absences to Unit4 timesheets via browser automation
Author: Pitra Pamungkas
Latest version: 1.0.2

# @pitra190/unit4-timesheet-mcp

Automate filling timesheets in **Unit4 Business World** by chatting with AI. This MCP server works with Claude Desktop, Claude Code, Cursor, and other MCP-compatible clients.

Instead of manually filling timesheets every month, just say:
> "Sync timesheets for March 2026"

And the tool will open a browser, log in, and fill everything in for you.

## Quick Start

### Step 1: Install Node.js

Download and install from [nodejs.org](https://nodejs.org/) (choose the LTS version).

### Step 2: Run the setup wizard

Open your terminal (Terminal on Mac, Command Prompt on Windows) and run:

```bash
npx @pitra190/unit4-timesheet-mcp init
```

This will automatically:
- Install the browser needed for automation
- Create a `~/unit4-config/` folder with template config files
- Print the MCP config block you'll need in Step 4

### Step 3: Edit your config files

Open the `~/unit4-config/` folder and edit the three files inside. See [Config Files Explained](#config-files-explained) below for details on each file.

### Step 4: Connect to your AI chat client

Copy the config block that was printed in Step 2 into your AI client's config file. Replace the placeholder values with your actual Unit4 credentials.

**Where to paste it:**

| Client | Config file location |
|--------|---------------------|
| Claude Desktop (Mac) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
| Claude Code | `.mcp.json` in your project folder |
| Cursor | Settings > MCP Servers |

The config block looks like this — replace the `YOUR_...` values:

```json
{
  "mcpServers": {
    "unit4-timesheet": {
      "command": "npx",
      "args": ["-y", "@pitra190/unit4-timesheet-mcp"],
      "env": {
        "UNIT4_LOGIN_URL": "https://ubw.unit4cloud.com/YOUR_INSTANCE/Login/Login.aspx",
        "UNIT4_USERNAME": "YOUR_USERNAME",
        "UNIT4_PASSWORD": "YOUR_PASSWORD",
        "UNIT4_BASE_PATH": "/Users/YOUR_NAME/unit4-config"
      }
    }
  }
}
```

### Step 5: Restart your AI client and start chatting

Try these:
- "Sync timesheets for March 2026"
- "Preview timesheet entries for John Doe"
- "Show the current sync status"

The tool will open a visible browser window so you can watch it fill in the timesheets.

---

## Config Files Explained

All config files live in `~/unit4-config/`. The setup wizard creates them with template values — you just need to fill in your actual data.

### config.json

This controls **what** gets filled in the timesheets and **when**.

```json
{
  "workOrder": "82010000100",
  "activities": {
    "regular": "078",
    "annual_leave": "611",
    "sick_leave": "820",
    "public_holiday": "611"
  },
  "leaveDescriptions": {
    "annual_leave": "annual leave",
    "sick_leave": "sick leave"
  },
  "hoursPerDay": 8,
  "setStatusReady": false,
  "dateRange": {
    "startDate": "2026-03-01",
    "endDate": "2026-03-31"
  },
  "holidays": [
    { "date": "2026-03-19", "description": "Nyepi Day" },
    { "date": "2026-04-03", "description": "Good Friday" }
  ]
}
```

| Field | What it means |
|-------|---------------|
| `workOrder` | Your Unit4 work order number |
| `activities.regular` | Activity code for normal working hours |
| `activities.annual_leave` | Activity code for annual leave |
| `activities.sick_leave` | Activity code for sick leave |
| `activities.public_holiday` | Activity code for public holidays |
| `leaveDescriptions` | Text shown in the description column for each leave type |
| `hoursPerDay` | Standard working hours per day (usually 8) |
| `setStatusReady` | `false` = save as Draft, `true` = save as Ready |
| `dateRange` | The month/period to process (update this each time you run) |
| `holidays` | List of public holidays — employees get these days off automatically |

### employee_mapping.json

This maps **employee names** to their **Unit4 display names**. Unit4 shows names in "Lastname, Firstname" format, which is different from how you normally refer to people.

```json
{
  "John Doe": {
    "unit4Name": "Doe, John",
    "workDescription": "Frontend dev"
  },
  "Jane Smith": {
    "unit4Name": "Smith, Jane Marie",
    "workDescription": "Backend dev"
  }
}
```

| Field | What it means |
|-------|---------------|
| Key (e.g., `"John Doe"`) | The name you use to refer to this person |
| `unit4Name` | The exact name as it appears in Unit4 (must match exactly!) |
| `workDescription` | Text shown in the description column for regular work hours |

To find someone's `unit4Name`, open Unit4 and look at how their name appears in the employee dropdown.

### absence_data.json

This lists **who is on leave and when**. If nobody has any leave, just leave it as an empty array `[]`.

```json
[
  {
    "absence_id": "1",
    "employee_name": "John Doe",
    "leave_type": "annual_leave",
    "start_date": "2026-03-10",
    "end_date": "2026-03-12",
    "duration_days": 3,
    "notes": "Family vacation",
    "department": "Engineering"
  },
  {
    "absence_id": "2",
    "employee_name": "Jane Smith",
    "leave_type": "sick_leave",
    "start_date": "2026-03-05",
    "end_date": "2026-03-05",
    "duration_days": 1,
    "notes": "",
    "department": "Engineering"
  }
]
```

| Field | What it means |
|-------|---------------|
| `absence_id` | A unique ID (can be any string) |
| `employee_name` | Must match a key in `employee_mapping.json` |
| `leave_type` | Either `"annual_leave"` or `"sick_leave"` |
| `start_date` | First day of leave (YYYY-MM-DD) |
| `end_date` | Last day of leave (YYYY-MM-DD) |
| `duration_days` | Number of working days (weekends don't count) |
| `notes` | Optional notes |
| `department` | Employee's department |

---

## Environment Variables

| Variable | Required | Description |
|----------|----------|-------------|
| `UNIT4_LOGIN_URL` | Yes | Your Unit4 login page URL |
| `UNIT4_USERNAME` | Yes | Unit4 username |
| `UNIT4_PASSWORD` | Yes | Unit4 password |
| `UNIT4_BASE_PATH` | Yes | Path to folder containing your config files |
| `UNIT4_ABSENCE_DATA_PATH` | No | Path to absence data JSON (defaults to `absence_data.json` in `UNIT4_BASE_PATH`) |

## Available Tools

| Tool | Description |
|------|-------------|
| `sync_absences_to_unit4` | Run the full sync — opens a browser and fills timesheets for all employees |
| `get_unit4_sync_status` | Check current configuration, mapped employees, and validate settings |
| `preview_timesheet_entries` | Preview what entries would be created without actually submitting |

## How It Works

The tool uses browser automation (Playwright) to:
1. Log into Unit4 Business World
2. Navigate to the timesheet form
3. For each employee and each week in the date range:
   - Fill in regular work hours for weekdays
   - Fill in leave hours for any absences
   - Fill in public holidays
   - Skip days that already have the correct hours
4. Save and take a screenshot for each employee/week

The browser window stays visible during the process so you can watch it work.