hibob-advanced-mcp
MCP server for HiBob's Workforce Planning API, Reports API, tasks, and employee lifecycle operations.
Search, create, update, cancel positions, openings, and budgets.
Resolve named lists and workforce form fields/options.
List, download, and generate saved reports (JSON/CSV/XLSX).
List and complete tasks; get employee tasks.
Find employees, read/update employee data, add records, terminate employees.
Read-only mode available (omits write tools).
Authenticates with a HiBob API service user; supports sandbox, proxies, rate limits.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@hibob-advanced-mcp show open positions in Engineering"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
hibob-advanced-mcp
An MCP server for HiBob's Workforce Planning API, tasks, employee termination, time off, and Reports API.
This complements a standard HiBob HRIS integration. Common HRIS functionality (people, documents) belongs in the main integration; this server adds planned positions, openings, budgets, tasks, time off and access to saved reports.
It runs over stdio, is installable with uvx, and authenticates with a HiBob API service user.
HiBob setup
In HiBob, go to Settings → Integrations → API service users and create a service user. HiBob shows the service user ID and token once — copy both now, as they cannot be retrieved later.
Create (or reuse) a permission group containing that service user, and grant it:
Features → Workforce planning → Position management → Manage positions
To use the tasks tools, also grant the service user access to the Tasks API (read, and complete for
hibob_complete_task).To terminate employees with
hibob_terminate_employee, grant People's data → People's fields → Edit on the Lifecycle category, plus View on employees' names and work emails so the employee can be looked up.Reading and changing employee data (
hibob_get_employee,hibob_update_employee) needs People's data → People's fields: View on the categories read, Edit on those changed, and View history to read a table's earlier rows. HiBob skips fields the service user may not edit without saying so; the update tool reads every change back to catch that.Time off needs People's Data → Time off: See who's out today → See who's out to list requests (plus See who's out because of a private policy or policies with a custom name for private ones), Balance → See people's time off and sick leave balances for balances, and Requests → Create, edit and cancel people's requests that haven't been approved yet to read a request by ID, book and cancel (and to list pending requests). Policy types, and booking, also need Features → Time off → Settings → Manage company's time off settings. HiBob's label stops at requests not yet approved, yet the demo tenant's service user cancelled approved ones; check this on your own account if approved leave must be cancellable.
Reports need Features → Reports → View reports according to people's data access rights, plus access to the employees and fields included in each report. Reports containing formulas also need Features → Formulas in Grids → View formulas in grids. Reports do not require the workforce planning permission.
Service users have no permissions by default. The server identifies the relevant permission when an endpoint denies access.
If your HiBob account restricts API access by IP address, allow the outbound IP of wherever this server runs.
Read-only workforce planning use still needs the same workforce planning grant. Use HIBOB_READ_ONLY=true (below) to omit employee/task/workforce mutation tools; report generation and downloads remain available.
Related MCP server: humaans-mcp
Configuration
Environment variable | Required | Description |
| yes | Service user ID (the Basic auth username). |
| yes | Service user token (the Basic auth password). |
| no | Defaults to production ( |
| no |
|
| no |
|
Standard proxy variables (HTTPS_PROXY, ALL_PROXY) are honoured. A SOCKS5 proxy needs the optional socks extra — see the install line below.
Hiding people data
Finding a person by name still works with HIBOB_HIDE_PEOPLE_DATA on, so that hibob_get_employee and hibob_find_employee stay useful. What changes:
hibob_get_employeereads only ID, display, full, first and last name, work email, work phone, work mobile and work site. With no fields named it returns the display name, email, phones and site. Any other field, such as job title, salary or a personal phone, is refused before HiBob is asked for it, and so ishistory.The job title is left out of the employee shown in every employee tool.
hibob_update_employeeandhibob_add_employee_recordstill write, but do not show what a change replaced (from) or what HiBob holds in place of a change it did not keep (read).hibob_list_employee_fieldsis unchanged: it describes fields and tables but holds no employee data.Of the time off tools only
hibob_list_timeoff_policy_typesis registered: the rest show who is off, sickness included.
Not covered: the workforce planning tools, tasks (hibob_list_open_tasks, hibob_get_employee_tasks) and reports. Reports can include any people field, so keep their separate HiBob permission off the service user if it must not reach that data. The lock limits what this server shows; the service user's HiBob permissions still decide what HiBob can return.
Running it
Pinned to a commit, which is how it should be deployed:
uvx --from 'git+https://github.com/JustParent/hibob-advanced-mcp@<GIT_SHA>' hibob-advanced-mcpFrom a local checkout, during development:
uvx --from . hibob-advanced-mcp --test--test prints the version, the resolved API base URL, whether credentials are set (never their values), the read-only state, and every registered tool, then exits. It verifies an install without needing an MCP client or live credentials.
With a SOCKS5 proxy:
uvx --from 'git+https://github.com/JustParent/hibob-advanced-mcp@<GIT_SHA>[socks]' hibob-advanced-mcpClaude Desktop
{
"mcpServers": {
"hibob-workforce-planning": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/JustParent/hibob-advanced-mcp@<GIT_SHA>",
"hibob-advanced-mcp"
],
"env": {
"HIBOB_SERVICE_USER_ID": "<service user ID>",
"HIBOB_SERVICE_USER_TOKEN": "<service user token>"
}
}
}
}Plugging into a sandboxed MCP integration
For a host that runs MCP servers as sandboxed subprocesses using the Claude Desktop config shape, the integration config is:
{
"server_type": "sandboxed",
"sandbox_command": "uvx",
"sandbox_args": [
"--from",
"git+https://github.com/JustParent/hibob-advanced-mcp@<GIT_SHA>",
"hibob-advanced-mcp"
],
"sandbox_runtime": "python",
"auth_type": "none",
"sandbox_env": {
"HIBOB_SERVICE_USER_ID": "<service user ID>",
"HIBOB_SERVICE_USER_TOKEN": "$SECRET_KEY"
}
}Paste the service user's token into the integration's secret key field: $SECRET_KEY is substituted with it inside the sandbox, so the token is never stored in the config itself. The service user ID is not a secret and goes in literally.
No --with 'mcp<2' argument is needed — this package pins the MCP SDK itself.
Tools
Field IDs are passed as flat mappings, for example {"/position/fte": 100}. The /position/ prefix may be omitted ({"fte": 100}). The server wraps values into HiBob's {"value": ...} envelope for you, and flattens search results back out.
Read
Tool | HiBob endpoint | Rate limit |
| metadata for | 50/min |
| metadata for each section of the form, plus | 50/min |
|
| — |
| metadata for the object, then | 50/min each |
|
| 100/min |
|
| 100/min |
|
| 100/min |
|
| 100/min |
|
| 100/min |
|
| 100/min |
| the same two searches, company-wide, aggregated in memory | 100/min |
|
| — |
|
| — |
|
| — |
|
| 50/min |
|
| 100/min reads, 50/min tables |
|
| 20/min |
|
| 20/min |
|
| 20/min |
|
| 20/min |
|
| — |
|
| — |
|
| — |
|
| — |
|
| — |
Search results come back as {"count": N, "entries": [{"values": {...}, "display": {...}}]}. values holds the raw values including the IDs the write tools need; display holds HiBob's human-readable labels. The opening and budget searches are cursor-paginated and return has_more and next_cursor; the budget search takes a limit up to 1000 (HiBob rejects anything larger), which is one page for most companies. Position search has no pagination and ignores limit entirely, so request only the fields you need and filter where you can.
Seven of the read tools do work HiBob's API cannot do in one request:
hibob_get_openings_for_positionsanswers "which openings belong to this position?". HiBob's opening search only filters by an opening's own ID, status or name, never by its parent position. The tool sends a filter every opening satisfies (/positionOpening/id notEqual "1", the clause verified against HiBob's sandbox), pages through every opening 100 at a time, and joins on/positionOpening/positionIdin memory. Pass several positions at once to pay for the scan once; astatusesfilter is applied by HiBob and shortens it. Positions may be given by numeric ID or by name (P-0000000368); names are resolved in one search first and reported back inresolved_positions. The result reportscounts_by_position, so a position with no openings shows as0, andscan_complete, which is false only if the scan hit its 10,000-opening safety cap.hibob_get_positions_underanswers "which positions report up to me, and which are filled?". Position search cannot filter by manager position or by holder, but it returns every position in one unpaginated response with its manager position and the employee filling it, so the tool fetches them all (one request) and walks the reporting tree in memory. The top position can be given as a position ID, a position name (P-...), the holder's HiBob employee ID, the holder's work email or the holder's name; those last two matter because HiBob has no call from an employee to their position, and a name fitting several people returnscandidatesinstead. An email costs one narrowly scoped call to HiBob's people search that asks for the employee ID and nothing else; it is the only use of the people API by the position tools;hibob_find_employeealso uses it, to turn an email into an employee ID for the tasks tools, andhibob_terminate_employeeto find the employee it terminates. The result lists each position beneath with its status, holder and own manager position,depthlevels down (1 for direct reports), withcounts_by_status; astatusesfilter is applied after the walk so a vacant position under a filled one is never lost.hibob_search_position_budgetsnames the position each budget belongs to. A budget record carries no reference to its position — HiBob's only link runs the other way, as/position/budgeton the position — so a budget fetched on its own genuinely cannot be attributed to anything, and the honest reading of one in isolation is that its cost belongs to nobody. The tool therefore scans every position for that reference and reports the owner as each entry'sposition. It sits besidevaluesrather than among the field IDs because it is synthesised here, and HiBob can neither filter nor sort on it.nullmeans no position references that budget; a failed lookup returns the budgets withposition_link_errorand nopositionkey at all, which is deliberately not the same asnull. Passinclude_position: falsefor a pure total to skip the extra request.hibob_get_position_costsanswers "what does this position cost?". Cost is not a field on a position./objects/position/searchexposes 38 fields and none of them are cost; the figures the HiBob UI shows on a position (expected base salary, total position cost, total converted cost, prorated cost) live on a separatepositionBudgetobject. The only link is/position/budgeton the position, anentity_referenceholding the budget's ID — a budget carries no position ID of its own, so the join only exists in that one direction. The tool fetches the named positions, reads that reference off each, fetches exactly those budgets and merges them, in two requests; positions may be given by numeric ID or by name (P-0000000368), and names cost one more search, reported back inresolved_positions. Positions whose budget is missing are listed inpositions_without_budgetrather than dropped.Two HiBob behaviours make this hard to discover, and both fail silently. Unrecognised field IDs are dropped without an error — asking the position search for
/positionBudget/totalPositionCostCurrencyValue, or for an entirely invented field, returns200 OKwith the key simply absent, so a request for cost looks like it worked and came back empty. And filtering is whitelisted: positions filter only on/position/status,/position/name,/position/hasOpenRequestsand/position/id, budgets only on/positionBudget/idand/positionBudget/proRatedCostPercentage. Anything else is a 400, so no cost figure can be filtered or grouped by HiBob at all.hibob_summarize_position_costsrolls that cost up across the company, or a department, or everything still vacant — the thing HiBob cannot do itself, since cost is neither filterable nor groupable. It fetches every position and every budget (two requests), joins them and aggregates here, optionally grouped bydepartment,site,status,jobProfileorcurrencyand restricted to givenstatuses. Only the converted figures are summed: HiBob reports each position's total in its own local currency, and a company can have many, so adding those together would produce a meaningless number, while the converted values share the company's reporting currency. If converted costs ever arrive in more than one currency,total_converted_costisnullandtotals_by_currencycarries a total per currency instead of one wrong number.hibob_resolve_list_valuesturns the option names a user chose back into the IDs HiBob wants. A form is offered by option name, and what comes back from it is the name; by then a bot may have lost the form response that paired names with IDs, and the ID of the list behind the field with it. The tool takes what is left, the field as an ID or its label (Locations for hiring,Site) and the names, finds the list from the field's metadata, fetches that one list, and matches each name exactly, ignoring case, against the items and, for a tree-shaped list, their path labels (Spain > Madrid - Office). It returnsvalues, the IDs in the order given and ready to submit, and says whether the field ismulti. A name matching nothing comes back underunmatchedwith the nearest items ascandidates; one that two items share comes back underambiguousrather than guessed at; a branch of a tree, which cannot be submitted, offers its leaves. Every list field on a form names this tool asresolve_with.hibob_get_workforce_formreturns everything needed to fill in a create form as one blob: forposition(the default) that is the position's fields plus the nested opening (required) and budget (optional) sections; forpositionOpeningorpositionBudgetjust that object. Every list-backed field (department, site, job profile, currency, ...) arrives with itsoptionsresolved from the company's named lists, including theidto submit (a list longer thanmax_options, 100 by default, is truncated, and the field then says to fetch the rest withhibob_get_company_named_lists); fields with a fixed vocabulary (position type, recruitment status, pay periods) carryallowed_values; each field says whether it isrequired, and fields HiBob sets itself are listed separately asread_only_fields. Each section names the write tool argument it maps to (position_fields,opening_fields,budget_fieldsorfields), so the filled-in form can be passed straight to the create tool.A form has to be complete when it is generated (a Slack form, say, needs every option up front), and job profiles and manager positions run to a thousand items. So for a position form the tool takes what the user should be asked first:
department,job_profile(a rough title) andmanager(a name,P-...position name or ID). A department pre-fills its field and narrows the manager choices to that department's branch of the position tree and the job profiles to those naming the department; a title or a manager's name picks out the matching leaves, and a single match pre-fills the field asvalue. Tree-shaped lists are offered as their leaves, labelled by path (Data > Head of Data > P-0001 · London · Jane Doe). If any of these three fields still cannot be offered withinmax_options, the response carriesquestions(each with the argument to pass, a question to ask the user and, when few enough,candidates) instead ofsections; answer them and call again.
Write (omitted when HIBOB_READ_ONLY is set)
Tool | HiBob endpoint | Rate limit |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min |
|
| 10/min (email, start date 20/min) |
|
| 10/min |
|
| 10/min |
|
| — |
|
| — |
Every tool that takes a position or an opening accepts it either as its numeric ID or as the name HiBob shows: P-0000000368 for a position, O-6853240227 for an opening. A name costs one search before the write, which comes out of the search limit rather than the write limit; a name that matches nothing, or that HiBob has let two records share, is refused rather than guessed at. Openings are always looked up before an update or delete, whether given by ID or by name, because the lookup returns the position each opening belongs to. A write addressed under the wrong position, such as O-6853240227 beneath the ID of another position, is refused before anything is sent. Budgets have no name and need none: a position carries its budget's ID as /position/budget, so hibob_update_position_budget finds the budget from the position, and its budget_id argument is optional. If a budget_id is given it must be that position's budget, so an ID carried over from another position is refused; a second budget for a position that already has one is refused likewise. Each result reports the numeric position_id (and opening_id or budget_id) actually written to.
Writes are limited to ten calls a minute, so required fields are validated before a request is sent and write calls are never retried automatically. Read calls retry twice on 429 and 5xx responses, honouring Retry-After. Named lists and metadata are each limited to fifty calls a minute and a position form needs a dozen lists and three metadata calls, so both are cached in the server process for five minutes, shared between the form, named-list and resolve tools; a failed fetch is not cached. Every create and update reads the record back through its search endpoint and returns it with verified; a read-back failure is reported as verification_error rather than as a failed write, and a created opening is checked to belong to the position it was created under.
hibob_create_position creates one position per call, together with its first opening (HiBob requires one) and an optional budget. HiBob's API cannot give a new position a budget end date, so a role that ends takes cancellation_date instead: once the position is created, its cancellation is scheduled for that day (a second write call, and the same endpoint as hibob_schedule_position_cancellation). The date must fall after the budget date and the opening's expected start date, and is checked before anything is sent; /position/endEffectiveDate in position_fields is refused with a pointer to it. If the scheduling fails after the create, the result still carries the new IDs, with verified: false and HiBob's reason.
hibob_cancel_position cancels a position now; HiBob refuses it for a filled position. hibob_schedule_position_cancellation cancels one on a later date instead, such as when a fixed-term role ends: until then the position shows as "Cancelled soon", and at midnight on the date HiBob cancels it and unassigns whoever holds it. HiBob's API documents no way to undo a scheduled cancellation, so the tool looks the position up first and refuses, before anything is sent, a position already cancelled or cancelled soon, a date that is not a real day today or later, and a filled, starting or departing position unless unassign_holder is true, the error naming the holder so the user can be asked. The position is read back afterwards with its status and budget end date.
hibob_terminate_employee adds a termination entry to an employee's lifecycle; HiBob changes their status to Terminated on the termination date. HiBob's API documents no way to undo a termination, so everything is checked before the one request is sent. The employee may be given by employee ID or work email and must match exactly one active employee, whose name the result reports. termination_reason and reason_type may be given by name (Resigned) or ID, and are matched exactly, ignoring case, against the company's terminationreason and lifecycleReasonType lists (HiBob's list names are case-sensitive); a name matching nothing, or several items, is refused with the closest items. last_day_of_work may not fall after termination_date, and a notice period needs both its length and its unit (days, weeks, month or years, as HiBob spells them). The tool does not revoke the employee's access to Bob, and nothing is read back afterwards: the result reports exactly what HiBob accepted.
hibob_update_employee takes changes as field labels or IDs mapped to values given by name, and works out where each goes: plain fields in one PUT /people/{id}, the work email and start date through their own endpoints. List values and people (by name, email or ID) are matched exactly, ignoring case; anything unmatched or ambiguous, and an amount without a currency, comes back as {"status": "needs_input", "questions": [...]} with candidates and nothing written. Fields HiBob keeps a dated history of need an effective_date: job title, department, site, manager ("Manager" and "Reports to" are the same column; give only one), change type (Promotion, Lateral Move, ...: without it HiBob tags the new row "Other"); employment contract, type, pay type, FLSA code and holiday calendar; salary amount, pay period and frequency; home address (lines 1 and 2, city, post code, state or region, country). Without one the tool asks for it and never assumes today. Each such change adds a new row to HiBob's work, employment, salary or address table. HiBob replaces a row wholesale (a column left out is stored empty), so the new row copies every column of the row in force before its date and lays the change over it. Nothing is written if HiBob hides part of the table from the service user, the employee already has a row on that date (existing rows are never edited), or there is no earlier row to copy (a first salary row is an exception: it needs the amount with its currency, the pay period and the pay frequency; a first address row is another: it is just what you give). A later row that still holds a different value comes back as a question; allow_later_rows: true goes ahead. A bare salary amount takes its currency from the row it copies, and a change that matches the row it would copy adds nothing. New rows carry HiBob's default change type; a reason is recorded on work, employment and address rows (the salary table has no reason column). Every new row is read back and compared column by column, and anything HiBob dropped is listed under unconfirmed. Working patterns cannot be changed yet, and the derived columns (FTE, weekly hours) are copied as they were. Fields HiBob derives (tenure, job architecture fields that follow the job profile, people analytics, lifecycle) are refused as not writable, and hibob_list_employee_fields says which. An effective_date other than today is refused for plain fields, which HiBob changes immediately. Writes go plain fields, start date, then email, each once; a failure stops the rest (partial). HiBob skips fields the service user may not edit without saying so (a live check saw a 200 for a request whose fifth field was not stored), so every change is read back, re-reading for about 10 seconds, and anything still not visible is listed under unconfirmed.
hibob_add_employee_record adds one row to the tables HiBob keeps several rows in: variable pay, entitlements and deductions (dated: the tool asks for the effective date), equity grants, training, bank accounts, dependents, right-to-work documents, and the company's custom tables. A new record copies nothing, so the columns it needs are asked for, with the options a list offers; a value matching nothing or several things comes back as a question; the type's list, column and date rules are in hibob_list_employee_fields under record_types. Entitlement and deduction types are sent by name, as HiBob wants. Calling again with the same values adds nothing (the identical record is reported), so a retry after a lost response is safe. Bank account, IBAN, routing and document numbers are sent in full and shown back masked to their last four characters, including in hibob_get_employee's history. Every record is read back and compared; a dropped column is listed under unconfirmed, and a dropped required column is called out (HiBob stores nothing for a currency it cannot read, such as a made-up code, yet still passes the mandatory check). HiBob's API has no way to edit or delete a record through this server. Bank accounts and right-to-work records need the sensitive-data write permission in HiBob.
Time off
The time off tools take an employee by ID, work email or name, and requests by HiBob's numeric request ID (requestId in hibob_list_timeoff_requests).
HiBob uses two vocabularies for a request's state. Who's out lists only live requests, as approved or pending; reading one request gives pending_approval, approved or canceled. A cancelled request drops out of who's out, so hibob_get_timeoff_changes is how to find one. HiBob's API cannot approve or decline a request.
hibob_cancel_timeoff_request works around a HiBob behaviour found live: DELETE /timeoff/employees/{id}/requests/{requestId} cancels by request ID alone and ignores the employee in the path, so it cancels anyone's request. A GET of the same path does check, answering 404 for another employee's request, so the tool reads the request under the employee first and sends nothing unless it is theirs. It then:
reports a request already
canceledwithout sending anything (HiBob would answer 200 and change nothing);refuses any state other than
pending_approvalorapproved;refuses a request that started before today unless
cancel_startedis true. HiBob would cancel it, past or in progress, pending or approved, but the whole request goes, days already passed included;sends the
DELETEonce and reads the request back to confirmcanceled. HiBob cancels at once, with no approval step for the cancellation.
hibob_submit_timeoff_request books whole and half days (afternoon to start at midday, morning to finish at midday; the same portion at both ends for a single half day). HiBob checks the request against the policy and its refusals are passed on in full, such as a conflict naming the request it overlaps, a request on non-working days only, or a missing reason code. skip_manager_approval approves the request at once. The new request is read back to report its status.
Reports
Configure a report and its filters in Bob, then call hibob_list_reports to
find its numeric ID. The API returns only reports and data visible to the
service user; a successful response does not prove the service user can see
the entire workforce or every requested field.
For programmatic use, download JSON with machine-readable values:
{"report_id": 12345, "format": "json", "include_info": true}Pass this to hibob_download_report. The result contains status: "ready",
the parsed data, byte_count, and a SHA-256 checksum of the downloaded
bytes. human_readable: "APPEND" includes labels alongside IDs;
"REPLACE" replaces IDs with labels. This option is valid only for JSON.
An optional locale selects the report's column language.
CSV is returned as text, preserving leading zeroes, duplicate column names,
metadata rows and embedded newlines. XLSX is returned as data_base64 with
encoding: "base64". The server does not write files or silently truncate
reports. It enforces a 10 MiB decoded response limit, including compressed and
chunked downloads. Narrow the saved report if it exceeds that limit. Consume
large outputs in code and pass summaries to the agent to control context size.
For reports that take longer to generate:
Call
hibob_generate_reportwithreport_id,format(csvorxlsx), and optionallyinclude_info/locale.Persist the returned
report_nameandformat.status: "accepted"means generation has started, not that the file is ready.Call
hibob_download_generated_reportwith that name and format.status: "pending"means Bob returned 204; poll the same file later.status: "ready"contains the complete data in the format above.
Each call performs one poll, with no long-running sleep loop. Downloads retry transient HTTP errors using the shared read policy; starting generation is never automatically retried because another request can create another file. Respect the Reports API's 20 requests/minute per endpoint limit. A generated file's availability is controlled by Bob; persist successful downloads if you need reproducible inputs. There is no server-side report cache.
The async endpoint's documented format enum lists CSV/XLSX; JSON is exposed
through the direct download endpoint. The returned Location is used only
to extract a file name. Downloads always use the configured API host and never
follow redirects or send credentials to the supplied location's host.
Reports are saved queries, not a durable event log. The download APIs do not
accept an arbitrary since cursor: date conditions belong to the report in
Bob. Validate which fields, deleted rows and effective-date changes a report
actually includes before treating it as a change feed. Keep downstream
processing checkpoints separately.
Official API contracts: list, download, generate, poll, permissions and limits.
Field cheat sheet
Required to create a position:
Object | Required fields |
|
|
|
|
|
|
Updatable on a position: name, effectiveDate, managerPositionId, positionType, fte, employmentType, department, site, jobProfile, reason, plus custom fields (/position/field_<number>, IDs from hibob_get_workforce_form). Custom fields are passed to HiBob unverified: the result names them as undocumented_fields, a rejected update says they may be the reason, and a field HiBob accepted but did not keep shows up in unconfirmed_fields after the read-back. Fields HiBob sets itself (id, status, filledBy, ...) are refused before any request.
Filterable fields: /position/status, /position/name, /position/hasOpenRequests, /position/id; /positionOpening/id, /positionOpening/status (vacant, starting, filled, departing, cancelled, onHold, cancelledSoon), /positionOpening/positionOpeningName. A search without filters returns everything: HiBob refuses an empty filter list, so the server sends a clause every record satisfies.
Fields such as department, site and jobProfile take HiBob list item IDs, not names. hibob_get_workforce_form returns those IDs alongside each field; hibob_get_company_named_lists returns one list's items, or with no list_name just the names and sizes of every list, since the full contents of every list can run to tens of megabytes.
HiBob refuses a write value of the wrong JSON type, at least sometimes with a bare 400 that names no field, so the write tools send each documented field as the type HiBob's API reference gives it. A value is converted where that is unambiguous and refused before the request otherwise:
Fields | HiBob wants | Converted | Refused |
| a number | digits in a string, | names, decimals, anything else |
| a number | a plain decimal in a string, |
|
|
| surrounding spaces | the day-first dates HiBob's searches display ( |
| a string | a whole number, | anything else |
null is refused for all of them: HiBob allows it only on some creates, where leaving the field out does the same, and never on update. Custom fields (/position/field_<number>) go through as given, since HiBob's reference does not type them.
Tool arguments that name a position, opening, budget or list item take a number as readily as a string, since results show those IDs as numbers. Search filter values go to HiBob as strings, as it compares them, with true/false for yes/no fields.
Development
uv venv
uv pip install -e '.[test,lint,typecheck]'
pytestLint, formatting and types are enforced in CI:
ruff check . # add --fix to apply the automatic fixes
ruff format . # CI runs --check, so format before pushing
mypy # non-strict; paths come from pyproject.tomlType checking is deliberately non-strict — annotations are checked where they
exist, but untyped code is allowed. The package ships a py.typed marker, so
its annotations are visible to anything that imports it.
Inspect the tools interactively:
npx @modelcontextprotocol/inspector uvx --from . hibob-advanced-mcpLicense
MIT
Available Tools
13 toolshibob_cancel_positionADestructiveIdempotent
Cancel a planned position, removing it from the workforce plan.
HiBob refuses to cancel a position that is currently filled; check '/position/status' with hibob_search_positions first. Cancelling cannot be undone through this API, so confirm the position ID before calling.
Args: position_id: The position's ID.
Returns: str: JSON confirming the cancellation, or an error message beginning with "Error:".
Rate limit: 10 requests/minute.
Inputs: position_id (required). Returns: result
hibob_create_positionA
Create a planned position, with its opening and optional budget.
A position is a budgeted seat in the plan; every position must be created with an opening, which is the vacancy to be filled. Field values that reference HiBob lists (department, site, job profile) must be the list item IDs - resolve them first with hibob_get_company_named_lists and hibob_list_workforce_fields.
Creates one position per call. Required fields are checked before the request is sent, because HiBob allows only ten write calls per minute.
Args: position_fields: Flat mapping of position field IDs to values. opening_fields: Flat mapping for the nested opening. budget_fields: Optional flat mapping for the nested budget.
Returns: str: JSON {"id": int, "positionOpeningId": int} identifying the new position, or an error message beginning with "Error:".
Examples: - "Plan a new engineer starting in September" -> position_fields with effectiveDate/fte/department/site/jobProfile plus opening_fields with expectedStartDate. - Don't use when: adding a second vacancy to an existing position (use hibob_create_position_opening).
Rate limit: 10 requests/minute.
Inputs: opening_fields (required), position_fields (required), budget_fields. Returns: result
hibob_create_position_budgetA
Attach a salary and cost budget to a position.
Args: position_id: The position's ID. fields: Flat mapping of budget field IDs to values.
Returns: str: JSON {"positionBudgetId": int}, or an error message beginning with "Error:".
Examples: - "Budget 65k a year for that role" -> fields with expectedBaseSalaryCurrencyValue, salaryPayPeriod and currency.
Rate limit: 10 requests/minute.
Inputs: fields (required), position_id (required). Returns: result
hibob_create_position_openingA
Add a vacancy to an existing position.
Args: position_id: The parent position's ID. fields: Flat mapping of opening field IDs to values.
Returns: str: JSON {"id": int, "positionOpeningId": int}, or an error message beginning with "Error:".
Rate limit: 10 requests/minute.
Inputs: fields (required), position_id (required). Returns: result
hibob_delete_position_openingADestructiveIdempotent
Permanently remove an opening from a position.
This deletes the vacancy record in HiBob and cannot be undone through this API. Confirm the opening ID with hibob_search_position_openings before calling.
Args: position_id: Parent position ID. opening_id: The opening's ID.
Returns: str: JSON confirming the deletion, or an error message beginning with "Error:".
Rate limit: 10 requests/minute.
Inputs: opening_id (required), position_id (required). Returns: result
hibob_get_company_named_listsARead-onlyIdempotent
Look up the allowed values of HiBob's named lists.
Position fields such as department, site and employment type must be set to a list item from HiBob's named lists rather than to free text. This tool resolves those names to the IDs that hibob_create_position and hibob_update_position expect.
Args: list_name: A single list to fetch, or None for all lists.
Returns: str: JSON mapping list names to their items, each with an ID and a display name.
Examples: - "Which departments exist?" -> list_name='department' - Use before hibob_create_position to turn "Engineering" into its list item ID.
Inputs: list_name. Returns: result
hibob_list_workforce_fieldsARead-onlyIdempotent
List the fields available on a HiBob workforce planning object.
Call this before searching or creating anything: it returns the field IDs (such as '/position/fte') that every other tool in this server expects, along with each field's type and whether it is required.
Args: object_type: 'position', 'positionOpening' or 'positionBudget'.
Returns: str: JSON describing the available fields, as returned by HiBob.
Examples: - "What can I set on a position?" -> object_type='position' - "What does a budget need?" -> object_type='positionBudget' - Don't use when: you need the allowed values of a list field such as department or site (use hibob_get_company_named_lists).
Rate limit: 50 requests/minute.
Inputs: object_type. Returns: result
hibob_search_position_budgetsARead-onlyIdempotent
Search position budgets: planned salary and total cost per position.
Use this for cost roll-ups across planned headcount, such as the total budgeted cost of every vacant position in a department.
Args: fields: Field IDs to return (1-50). filters: Optional filter clauses. limit: Page size, 1-100. cursor: Cursor from a previous page, or None to start. include_human_readable: Include display labels.
Returns: str: JSON of the form {"count": int, "entries": [...], "has_more": bool, "next_cursor": str}.
Rate limit: 100 requests/minute.
Inputs: fields (required), limit, cursor, filters, +1 more. Returns: result
hibob_search_position_openingsARead-onlyIdempotent
Search position openings, the vacancies attached to positions.
Openings carry the recruitment view of a position: expected start date, recruitment status and whether the seat is vacant, starting, filled or departing.
Args: fields: Field IDs to return (1-50). filters: Optional filter clauses. limit: Page size, 1-100. cursor: Cursor from a previous page, or None to start. include_human_readable: Include display labels.
Returns: str: JSON of the form {"count": int, "entries": [...], "has_more": bool, "next_cursor": str}. When "has_more" is true, call again passing "next_cursor" to retrieve the rest.
Examples: - "Which openings are still vacant?" -> filters=[{field_id: '/positionOpening/status', operator: 'equals', values: ['vacant']}]
Rate limit: 100 requests/minute.
Inputs: fields (required), limit, cursor, filters, +1 more. Returns: result
hibob_search_positionsARead-onlyIdempotent
Search the company's positions.
Returns one entry per matching position. Each entry has 'values' (the raw values, including the IDs needed by the update tools) and 'display' (human-readable labels).
This endpoint has no pagination, so always request only the fields you need and filter where possible in a large organization.
Args: fields: Field IDs to return (1-50). filters: Optional filter clauses combined by HiBob. include_human_readable: Include display labels alongside raw values.
Returns: str: JSON of the form {"count": int, "entries": [{"values": {...}, "display": {...}}]}, or an error message beginning with "Error:".
Examples: - "Which positions are vacant?" -> fields=['/position/id', '/position/name'], filters=[{field_id: '/position/status', operator: 'equals', values: ['vacant']}] - Don't use when: you need opening-level detail such as expected start dates (use hibob_search_position_openings).
Rate limit: 100 requests/minute.
Inputs: fields (required), filters, include_human_readable. Returns: result
hibob_update_positionAIdempotent
Change details of an existing position.
Only the fields supplied are modified. Use hibob_search_positions to find the position ID first.
Args: position_id: The position's ID. fields: Flat mapping of field IDs to new values.
Returns: str: JSON confirming the update, or an error message beginning with "Error:".
Examples: - "Move that position's start to October" -> fields with '/position/effectiveDate'.
Rate limit: 10 requests/minute.
Inputs: fields (required), position_id (required). Returns: result
hibob_update_position_budgetAIdempotent
Change an existing position budget.
Use hibob_search_position_budgets to find the budget ID.
Args: position_id: Parent position ID. budget_id: The budget's ID. fields: Flat mapping of field IDs to new values.
Returns: str: JSON confirming the update, or an error message beginning with "Error:".
Rate limit: 10 requests/minute.
Inputs: fields (required), budget_id (required), position_id (required). Returns: result
hibob_update_position_openingAIdempotent
Change an existing opening, such as its expected start date or recruitment status.
Args: position_id: Parent position ID. opening_id: The opening's ID. fields: Flat mapping of field IDs to new values.
Returns: str: JSON confirming the update, or an error message beginning with "Error:".
Rate limit: 10 requests/minute.
Inputs: fields (required), opening_id (required), position_id (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
Each tool targets a distinct resource-action pair: positions, openings, budgets, and metadata lookups are clearly separated. The naming and descriptions make it easy to pick the right tool, even for close operations like create_position vs create_position_opening.
All tools follow a consistent hibob_verb_noun pattern with snake_case throughout, e.g., hibob_search_positions, hibob_create_position_budget, hibob_delete_position_opening. Resource terms are plural and actions are predictable across the set.
Thirteen tools is well-scoped for a workforce planning server covering positions, openings, budgets, and supporting metadata lookups. Each tool earns its place and the count is neither bloated nor thin.
The server covers core lifecycle operations for positions, openings, and budgets, including search, create, update, and delete/cancel where relevant. A minor gap is the absence of a delete operation for position budgets, though create and update are covered.
Maintenance
Related MCP Connectors
MCP server for Arcoro and ExakTime — workforce data, jobs, cost codes and locations.
isolved and ApplicantPro jobs, tenant discovery, and change detection as an MCP server.
MCP server for SmartHR — employees, departments, dependents and employee invitations.
MCP server exposing klokin time-tracking operations (employees, time entries, stores) to AI clients.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP Server that provides access to Personio's HR and personnel data through the Personnel API, allowing interaction with employee records, HR systems, and personnel management functions.-
- AlicenseBqualityDmaintenanceRead-only MCP server for the Humaans HRIS API, providing tools to query people, reporting chains, compensation, time away, and more.33MIT
- FlicenseAqualityDmaintenanceRead-only MCP server for the Tipsoi HRM API, exposing 15 tools to read employee data, attendance, leave, overtime, and more.153-
- AlicenseNot gradedqualityDmaintenanceThis MCP server enables AI-assisted professional profile generation by connecting to verified employee work data (skills, career history, certifications, projects) from employer HCM systems.MIT