Woffu Manager
FreeNot checkedAn MCP server to manage time tracking and time off on Woffu, allowing users to clock in days, view balances, request leave, and check team availability.
About
An MCP server to manage time tracking and time off on Woffu, allowing users to clock in days, view balances, request leave, and check team availability.
README
An MCP server for Woffu: clock in the days you have left, see what time off you have and when it expires, and tell how much of your team is actually around next sprint.
Ask your agent, in whatever words you use:
- "Which days haven't I clocked in this month?" — then confirm, and it fills them.
- "How many holidays do I have left, and when do they expire?"
- "Book me the 14th of August off" — then confirm, and it files the request.
- "Who is off in August?" / "How much of the team is around next sprint?"
- "Is Maria away the week of the 20th?"
- "When is the next public holiday?"
Nothing is written to your timesheet or filed on your behalf without you seeing it first.
Tools
Read-only unless it says otherwise.
| Tool | What it answers |
|---|---|
list_pending_days(month?) |
Workdays with no hours logged, each with the exact time ranges that would be sent. Plus lockedDays: unfilled days Woffu no longer lets you edit. |
clock_in_days(dates[], ranges?) |
Writes. Clocks in the days you name. Every one is re-checked against Woffu first, and refused with a reason rather than a blanket "not pending". |
get_time_off_balance() |
What is left in each bucket and when it expires — soonest first, because whatever is left in a bucket is lost on that date. |
list_time_off_requests(status?) |
Your requests: pending, approved or denied. Each carries its requestId, which is what edit and cancel take. |
request_time_off(type, from, to?, startTime?, endTime?, comment?) |
Writes. Files a request for a leave type, validated against Woffu first. Whole-day types take a date range; hourly types take a day and a time span. |
edit_time_off_request(requestId, from?, to?, startTime?, endTime?, comment?) |
Writes. Changes a request's dates, times or comment. The leave type cannot change — cancel and file anew for that. |
cancel_time_off_request(requestId) |
Writes. Withdraws a request; a pending one is deleted and its balance returned. |
list_holidays(from?) |
The public holidays ahead. One costs no days from any bucket, and one next to a weekend makes a long weekend. |
get_team_availability(from?, to?, team?, person?) |
How many days each teammate is around over a range, and how many they are off. For sizing a sprint before committing to it. |
Clocking in
clock_in_days needs an explicit list of dates — there is no "clock in everything",
so you always see what is about to be logged. ranges overrides the schedule when you
want specific hours ([{"in": "09:00", "out": "13:30"}, ...]); omit it and each day
uses the theoretical schedule Woffu itself proposes, which is nearly always what you
meant. A weekend, a public holiday, an absence, a day already logged or one Woffu has
locked is refused by name, so you can tell which of those it was.
Working a public holiday is possible in Woffu, but only by filing a "work on a holiday" request. That is not implemented, so holidays are simply refused.
Requesting time off
request_time_off files a request for a leave type by name — the same names
get_time_off_balance lists ("Vacaciones", "Horas Ajuste Calendario"...). Whole-day
types take from and an optional to; the day count is whatever Woffu charges after its
own weekends and holidays, so a Friday-to-Monday range costs two days, not four. Hourly
types take a single from with startTime and endTime instead.
Every request is checked against Woffu before anything is written: an overdrawn balance, a weekend or a public holiday is refused with a reason rather than filed. Types that need a supporting document, and half-day requests, are out of scope — file those in Woffu directly.
edit_time_off_request and cancel_time_off_request both take a requestId from
list_time_off_requests. Editing changes dates, times or the comment; the leave type is
fixed, so to change that you cancel and file a new one. Cancelling a pending request
deletes it and returns the balance it held.
Team availability
Per person: workingDays in the range (weekends and public holidays already out),
daysOff already approved, pendingDaysOff still awaiting approval, and
availableDays — working days minus approved time off. totals carries the whole
team's capacity in person-days. Least-available first, since those are the people who
constrain the sprint.
Pending absences are not subtracted from availableDays: they are the risk, not the
plan, so they are reported next to it. Woffu does not count them as absent either.
It reads your own team by default — no id, no name, nothing to configure. Name
another team to look at it instead, or a person to narrow the answer to one of them.
Install
Nothing to download. The server is published to npm, so npx fetches and runs it — the
package name is the whole prerequisite:
claude mcp add woffu \
-e WOFFU_HOST=yourcompany.woffu.com \
-e [email protected] \
-e WOFFU_PASSWORD='...' \
-- npx -y woffu-adapter-mcp
npx -y woffu-adapter-mcp always pulls the latest published version. Pin it if you would
rather it did not move under you: [email protected] takes any published version.
Configuration
| Variable | Required | Example | What it is |
|---|---|---|---|
WOFFU_HOST |
Yes | yourcompany.woffu.com |
Your Woffu domain. Paste the whole URL if it is easier; only the host is kept. |
WOFFU_USERNAME |
Yes | [email protected] |
Your Woffu login. |
WOFFU_PASSWORD |
Yes | ******** |
Used once, to request a token. Never stored. |
WOFFU_TEAM |
No | platform |
Only to make get_team_availability default to a team other than your own. Your own is read off your profile. |
WOFFU_TZ |
No | Europe/Madrid |
The office time zone, any IANA name. It decides which instant a 09:00 clock-in lands on. Defaults to Europe/Madrid. |
The password is only used to request a token and is never stored. The token is cached,
in ~/.woffu-manager/token.json (mode 600), and Woffu issues it with a one-year lifetime
— so logging in happens once. Delete that file to force a new login.
GitHub Copilot, in VS Code
Put this in .vscode/mcp.json (or the user-level one, via MCP: Open User Configuration), then hit the Start action the file shows above the entry. Copilot
says servers, not mcpServers, and a local server has to declare "type": "stdio".
The password goes in as an input, so VS Code asks once, masks it and keeps it in the OS keychain — which means the file can be committed with the rest of the project without a password in it:
{
"inputs": [
{
"type": "promptString",
"id": "woffu-password",
"description": "Woffu password",
"password": true
}
],
"servers": {
"woffu": {
"type": "stdio",
"command": "npx",
"args": ["-y", "woffu-adapter-mcp"],
"env": {
"WOFFU_HOST": "yourcompany.woffu.com",
"WOFFU_USERNAME": "[email protected]",
"WOFFU_PASSWORD": "${input:woffu-password}"
}
}
}
}
The tools show up under the 🛠️ icon in Copilot Chat, in Agent mode.
Any other MCP client
{
"mcpServers": {
"woffu": {
"command": "npx",
"args": ["-y", "woffu-adapter-mcp"],
"env": {
"WOFFU_HOST": "yourcompany.woffu.com",
"WOFFU_USERNAME": "[email protected]",
"WOFFU_PASSWORD": "..."
}
}
}
}
Clone it only to work on it:
git clone [email protected]:alechagonzalo/woffu-adapter-mcp.git
cd woffu-adapter-mcp
npm install && npm test
What counts as "pending"
Two layers, because neither is enough on its own:
- From the month summary: weekends, holidays, events, absences
(
absenceEvents/pendingAbsenceEvents: leave, adjustment days, sick leave), future days and days with hours already worked are all excluded.isUserEditableis the hard gate — Woffu refuses writes wherever it isfalse. - From the day's slots: a day that was clocked in but not yet confirmed still
shows zero hours in the summary. What gives it away is a real
signIdon its slots. Without this second check the same day would be clocked in twice.
Today is included, even mid-workday: its full theoretical schedule gets logged.
The Woffu API, in case it needs revisiting
POST /api/svc/accounts/authorization/token— OAuth2 login (grant_type=password). ReturnsaccessToken(a JWT carrying theUserId, which is where we read it from) andexpiresIn.GET /api/svc/core/diariesquery/users/{id}/diaries/summary/presence— days in a month.GET /api/svc/core/diariesquery/diarysummaries/{diarySummaryId}/workday/slots/self— a day's exact time ranges. The summary only gives first entry, last exit and total break; it never says where the break falls. Only this endpoint knows a workday is 09:00–13:30 plus 14:00–17:53. The slots come back withsignId: 0: they are the proposal the UI preloads, not actual clocked time.PUT /api/svc/core/users/{id}/diarysummaries/workday/slots/self— saves. Times travel in UTC,signTypegoes from 1 (proposal) to 3 (manual clock-in, which is how Woffu displays them), andsignStatusto 1. It answers 200 with an empty body.GET /api/users/calendar-events/next— upcoming holidays.Typeis 2 on every one seen so far; other values are surfaced rather than assumed to be holidays.GET /api/user-agreement-events/availability— time-off balance per bucket. Carries no dates.POST /api/users/allocations/row— the allocations behind one bucket, and the only place the expiry date lives. Body:{agreementEventId, pageIndex, pageSize, createdOnFilter: {filterType: "CurrentCycle"}, affectedCycleFilter: "CurrentCycle"}. The cycle filter matters:/allocations/tablereports across cycles and disagrees with the availability endpoint (12 days vs 9), while the current cycle agrees.GET /api/users/{id}— the profile.DepartmentIdis the user's team, and it is the same number the calendar wants as anEntityId, so nobody has to configure their own team.DepartmentNamecomes back empty; only the calendar spells the name out.GET /api/scope/search?query=&types=999678— resolves a team name to the{EntityId, EntityType}pair the calendar wants; a team answers withEntityType32.typesis a bitmask over the scopes the calendar's own search box asks for.POST /api/svc/core/calendar— the shared calendar: one entry per person, one per day, already clipped to the range.visibilitiesis not optional: with an empty list Woffu answers with the whole company rather than the team.keywordmatches a person's name.requestStates: 3brings back approved and pending absences. No total comes back, so paging runs until a short page.eventDuration/absenceEventsDurationare an enum, not a count of days:1is a full day and2is an hours-only absence (isTimeOnly: true,startDate"09:00" rather than a date). Summing them books a half-day as two days off —isTimeOnlyis what tells them apart. Absences do land on weekends and holidays, and cost nobody a day.presenceEventsis telework: the person is still available.POST /api/svc/core/calendar/totals-simple— presents/absents per day. Unused: it counts every weekend as "everyone absent". The per-person calendar above agrees with it exactly on working days, once the same absences are counted.GET /api/users/requests/list?statusType=— time-off requests.statusTypeis 10 pending, 20 approved, 30 denied; it is required, so all three are fetched to list everything. Amounts arrive as{Resource, Values}, where the resource name is the only hint at the unit, and hours come split as["8", "30"]meaning 8h30m.
scripts/inspect.mjs /api/some/endpoint dumps any endpoint using the cached token —
useful when reverse-engineering a new surface.
npm test covers the filters, the day-counting rules, and that the payload matches,
field for field, the one the web app sends.
Install Woffu Manager in Claude Desktop, Claude Code & Cursor
unyly install woffu-managerInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add woffu-manager -- npx -y woffu-adapter-mcpFAQ
Is Woffu Manager MCP free?
Yes, Woffu Manager MCP is free — one-click install via Unyly at no cost.
Does Woffu Manager need an API key?
No, Woffu Manager runs without API keys or environment variables.
Is Woffu Manager hosted or self-hosted?
A hosted option is available: Unyly runs the server in the cloud, no local setup required.
How do I install Woffu Manager in Claude Desktop, Claude Code or Cursor?
Open Woffu Manager on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Woffu Manager with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
