Skip to main content
Glama
dragosh29

Timesheet Portal MCP server

by dragosh29

Timesheet Portal MCP server

An MCP server that lets Claude, ChatGPT and other MCP clients read a Timesheet Portal account: timesheet (time and cost) reports, invoices, leave bookings and balances, projects or jobs, clients, users and cost centres. It supports both editions of the product, Standard (Project) and Recruitment, and it is read-only. It is built from Timesheet Portal's public API documentation and its two published Swagger 2.0 specs.

Once it's connected, an administrator can ask things like:

  • "How many hours did the team log on the Acme account last week, and how much is that at the charge rate?"

  • "Which timesheets for October are still only submitted, not approved?"

  • "List this month's client invoices that are still in draft."

  • "Who is off next week?"

  • "Who has the most annual leave left this year?"

  • "Which placements for Beta Logistics end before Christmas?" (Recruitment edition)

Tools

Tool

Edition

What it does

API calls

list_timesheets

both

Time recorded for a date range, summed per task and employee (Standard) or employee and job (Recruitment) for each period of the chosen time grouping, with status, client, project, task, approver, quantity, units, rate (Standard) and charge. Filters: statuses, client, cost centre, employee group, employee/project/task (Standard) or contractor/job (Recruitment), approval dates, modified after. One page per call.

POST /reports/timesheets

list_invoices

both

Invoices dated in a range: number, status, dates, client, project, task, worker, currency, net, tax and total, and optionally the line items. Client invoices by default; client credit notes, self-billing invoices or self-billing credit notes on request.

POST /invoices

list_leave

Standard

Leave bookings in a date range: employee, leave category, status, start and end dates, working days, units, request and review dates. Filters: statuses, employee, employee group, modified after. The comments only on request.

POST /leavebookings/reports/bookings

list_leave_balances

Standard

Every employee's leave year: allowance, carried over, approved booked leave, non-deductible leave, adjustments and end-of-year balance.

GET /leavebookings/summary

list_projects

Standard

Projects with client, category, dates, charge budget, manager, cost centre and purchase order.

GET /projects

list_jobs

Recruitment

Jobs (placements) with client, category, cost centre, dates, frequencies, owner, purchase order and assigned contractors (names and codes).

GET /jobs

list_clients

both

Clients with category, cost centre, currency, payment terms, invoicing settings and notes.

GET /clients

list_users

both

Users with name, job title, role, employment type, group, home cost centre and line manager.

GET /users

list_cost_centres

both

Cost centre codes and descriptions.

GET /costcentres

list_timesheets, list_invoices and list_leave call endpoints that are POSTs in the API but only read: they take the report settings in the body and change nothing. They carry the MCP readOnlyHint annotation like the other tools.

Every tool except list_cost_centres takes max_results (100 by default; at most 1,000, or 500 for invoices) and says how many records it left out and how to narrow the query.

Report field names are documented in both specs, as an enum of 1,272 names shared by ReportSettingsModel, LeaveReportSettings and the other report settings, but without descriptions. The fields and groupings list_timesheets asks for are the ones in each edition's documented example request for POST /reports/timesheets, whose use in that report the vendor shows, minus the pay columns unless they are requested. POST /leavebookings/reports/bookings has no example request; list_leave asks for EmployeeReference, EmployeeName, LeaveCategory, LeaveStatus, LeaveStartDate, LeaveEndDate, LeaveTotalWorkingDays, LeaveUnits, LeaveRequestDate and LeaveReviewedDate, names from that enum that plainly say what they hold, and LeaveComments only with include_contact_details.

Not covered on purpose:

  • Writes. The only write this prototype would have offered is approving or rejecting a timesheet, and neither spec documents an endpoint for that. Invoice approval, emailing and exporting (/invoiceaction/*), marking timesheets exported (/timesheetaction/export), every "Add or update multiple" endpoint and DELETE /jobs are left out, so there is no write flag.

  • Expense reports (POST /expenseentries, POST /expenseforms): the request is a documented ReportSettingsModel (its field enum includes 66 Expense… names), but the only documented response is a 201 whose ResponseModel.returnObject is an untyped object, so there is no documented shape to read the report from. (The spec's V2 expense definitions are not used by any documented path.)

  • The invoice reports (/reports/invoicereport, /reports/invoicereport2): list_invoices already reads invoices from POST /invoices, whose records are typed (InvoiceModel), which makes it clear which values are amounts, pay or free text; the report endpoints return untyped rows of strings.

  • Custom reports (/reports/customreports, /reports/customreport): their columns are defined by each account and can include bank or pay data that this server could not reliably withhold.

  • Files: expense receipts and invoice PDFs are never downloaded.

  • GET /users/search and GET /contractors/search (documented as GETs with a request body, which standard HTTP clients cannot send; list_users with user_id covers a single lookup), GET /rates (pay rates), GET /thirdparties (includes bank accounts), GET /chargecodes (its id is typed as an integer while its example is a code), /reports/builtinreport, /reports/reportfields, /customfields, /logintoken, and the email/password POST /token route.

Related MCP server: Dezrez Rezi MCP server

Setup

Requires Node 18 or later.

npm install
npm run build

You need API credentials for your Timesheet Portal account: log in as a user with full administrator rights, go to Settings > Account > Account, open the API access tab, select a user account to associate with the credentials (Timesheet Portal says it should have the System Administrator role) and click Generate API credentials. The server uses the OAuth2 client-credentials flow only.

Claude Desktop: add this to claude_desktop_config.json:

{
  "mcpServers": {
    "timesheetportal": {
      "command": "node",
      "args": ["/absolute/path/to/timesheetportal-mcp/dist/index.js"],
      "env": {
        "TSP_CLIENT_ID": "your-client-id",
        "TSP_CLIENT_SECRET": "your-client-secret",
        "TSP_EDITION": "standard"
      }
    }
  }
}

Claude Code:

claude mcp add timesheetportal -e TSP_CLIENT_ID=your-client-id -e TSP_CLIENT_SECRET=your-client-secret -e TSP_EDITION=standard -- node /absolute/path/to/timesheetportal-mcp/dist/index.js

Variable

Required

Meaning

TSP_CLIENT_ID

yes

The API client id.

TSP_CLIENT_SECRET

yes

The API client secret.

TSP_EDITION

no

standard (the Standard / Project edition, default) or recruitment. It decides which tools are registered and which report fields are requested.

TSP_BASE_URL

no

Defaults to https://tenant.api.timesheetportal.com, the host the documentation names for the token endpoint and the API. The spec's security definitions name yourhost.api.timesheetportal.com instead; if your account has its own API host, set it here. Also used by the tests.

TSP_CALL_BUDGET_SECONDS

no

How long one tool call may spend walking pages (default 25, from 1 to 50): no new page is started after it, and the answer says where to continue. With the retry waits of the page in flight (about 20 s at most), a call stays under the 60-second default request timeout of MCP clients. One test uses 1.

TSP_REQUESTS_PER_MINUTE

no

How many requests per minute this server may send, spread evenly (default 30, the documented per-account limit, so one request every 2 seconds). Lower it if other integrations use the same account. Values above 30 exceed the documented limit and are only useful against a mock; the tests use 6000.

Safety defaults

  • Read-only. There are no write tools, whatever the environment says. Every tool carries readOnlyHint: true and destructiveHint: false. The only POSTs the server sends are POST /oauth/token, POST /reports/timesheets, POST /invoices and POST /leavebookings/reports/bookings, and the last three only read.

  • Personal data is withheld unless a tool is called with include_contact_details=true: email addresses, mobile numbers, dates of birth, gender and title, home addresses, join and leave dates, the accounting code (which the spec says "is usually used to reference payroll numbers") and a limited-company contractor's company details (users); client addresses, billing name and email, tax and company numbers (clients, which also sends include=address,billing only then); the billing contact, address and pay budget (projects); the billing contact, IR35 status, pay currency and frequency, pay budget and assigned pay and charge rates (jobs, which also sends Include=assignedrates only then; Include=assignedcontractorcodes is always sent, for the contractor codes); the leave comments (LeaveComments, free text that can hold the reason for an absence, not even requested from the API otherwise); the pay columns of the timesheet report (PayRate, TotalPay in Standard; TotalPay, NetMargin, GrossMargin, Deductions in Recruitment), which are not even requested from the API otherwise; and the amounts of self-billing invoices, which are what a contractor is paid (every invoice returned for a self-billing invoice_type counts as self-billing, since the invoiceSelfBilling flag is optional in the spec). Names of users, approvers, managers and workers, and the leave category (such as sickness) of a leave booking, are returned by default.

  • Never returned, even on request: bank details (the bankName, bankAccountName, branchCode, bankAccountNo, iBAN, swiftCode and bankCountryCode fields), the national insurance and tax number fields, the payroll data in employmentDetails, the HR data in personalDetails (next of kin, nationality, ethnic origin, marital status, passport number, home phone), custom field values (their content is defined by each account) and audit events. The includedFields parameter of GET /users (whose documented values include bankDetails and personalDetails) is never sent.

  • Free text (notes, descriptions including cost centre descriptions, timesheet notes, names, job titles, purchase orders, invoice external references, rate names, error messages, and every value of a report row) is redacted by default: email addresses become [email redacted], phone-number-like sequences [phone redacted] (the same heuristic as the other servers in this series: +/00 international numbers, bracketed UK area codes and UK 0… numbers of 9 to 11 digits), UK national insurance numbers [NI number redacted] and upper-case UK postcodes [postcode redacted]. Other digit strings that happen to look like these are redacted too; the raw text is available with include_contact_details. Card numbers (13 to 19 digits passing the Luhn check), IBANs, and sort codes or account numbers introduced by the words "sort code" or "account" are replaced with [card number redacted] or [bank details redacted] always, with or without include_contact_details. A bare 6- or 8-digit number cannot be told from a date or a reference and is left alone.

  • The report columns returned are only the ones the server asked for: anything else the API sends in a row is dropped. Rows of values are matched to the requested field names by position, so a row with more or fewer values than fields requested is refused with an error rather than returned under the wrong names.

  • Rate limits. The documentation's usage policy says, per account and in UTC windows: every request counts towards 30 requests per minute and 5,000 per day; "basic entity updating / creating" has 1,000 requests per hour, 2,000 per day, 500 record updates per hour and 3,000 per day; "Timesheet & Invoice Reports" have 24 requests per hour, 576 per day, 500 record reads per hour and 3,000 per day. A refused request gets 429 Too Many Requests with a Retry-After header giving the seconds until the limit resets, and the response states which limit was reached. This server spaces its requests by TSP_REQUESTS_PER_MINUTE (2 seconds apart by default). A 429 is retried at most twice, for any method, waiting for Retry-After (whole or fractional seconds, or an HTTP-date; 2 s then 4 s when absent). Each wait is capped at 10 seconds: if the API asks for longer (as it will for an hourly or daily limit), the call gives up at once and passes on how long to wait and the API's message.

  • 502, 503 and 504 are retried the same way for GET only (the suite exercises 502 and 503). A POST (the three report POSTs and the token request) is never retried after a gateway error; since the reports only read, the error says it is safe to call the tool again, and that each call counts against the report limits.

  • The usage policy says the API "is not intended to be a live data source" for dashboards that pull data on every refresh. The server's instructions tell the assistant about the limits and to answer with as few calls as possible; list_timesheets, list_invoices and list_leave fetch one request's worth of data per call and refuse ranges over 366 days before any request.

  • Paged lists (GET /projects, /clients, /users, /jobs) use the documented zero-based page parameter. Only /projects documents its page size (250). The walk stops at an empty page, at a page shorter than the documented size, or at a page shorter than the one before it; a lookup by code fetches one page. A page that repeats the previous page's records (an API ignoring page) is reported as incomplete rather than looped over. Each call fetches at most max_pages pages (2 for projects, 4 otherwise), stops once max_results records are in hand (a page is fetched whole, so the rest of the last page is left out and counted), and says where to continue.

  • Codes (client, project, job, user, cost centre, employee group, task) are checked before any request: 1 to 100 characters, no control characters, no surrounding spaces. Dates must be real YYYY-MM-DD dates; date-times are YYYY-MM-DDTHH:MM[:SS] without a zone.

  • The token is fetched with the documented form body, cached, refreshed before it expires (a minute before the end of its lifetime, which the documentation gives as an hour, or after half of it when the lifetime is under two minutes; the suite tests the latter with a 2-second token), and fetched afresh once when a call answers 401. The client secret and the token are scrubbed from any text the API echoes back. Rejected credentials produce a message that names TSP_CLIENT_ID and TSP_CLIENT_SECRET and where to generate them; a 403 says the credentials need a System Administrator user; an unknown code passes on the API's own message.

  • A 200 whose body is not JSON (a proxy or login page in the way), or is an object where the documented response is an array (a ResponseModel with success: false, say), is reported as an error naming TSP_BASE_URL and passing on the API's message, never as an empty list.

  • Time. A paged walk starts no new page once TSP_CALL_BUDGET_SECONDS (25 s) has passed, and says where to continue. A call the MCP client cancels, including when it times out, stops: the server passes the MCP request's abort signal to its API requests and its waits (throttle spacing and Retry-After), so the request in flight is aborted and nothing more is sent for that call. A token request already under way is left to finish, because concurrent calls share it.

Tests

npm test

The suite downloads both specs (docs/standardJson to spec-standard.json, docs/recruitmentJson to spec-recruitment.json) on the first run and then:

  1. Validates every fixture record against the definitions in the specs with Ajv in JSON Schema draft-04 mode (Swagger 2.0), for each edition that has the endpoint: ChargeCodeGroupModel (253 projects, crossing the documented page size), JobModel, ClientModel, EmployeeModel, CostCentreModel, InvoiceModel with InvoiceItemModel, LeaveSummaryModel, and the report rows against the documented POST /reports/timesheets and POST /leavebookings/reports/bookings responses (arrays of arrays of strings), with every leave field name checked against the LeaveReportSettings enum. The definitions allow any extra key, so a separate walk fails on any key a definition does not declare. The two editions' definitions are asserted identical. The vendor's own examples write date-times without a zone (2022-12-01T00:00:00), which strict RFC 3339 rejects, so date-time is checked with the zone optional; the suite proves the published example request fails the strict format and passes this one (after leaving out the null filters it sends, which Swagger 2.0 cannot declare). It also checks that the mock's client id and secret are obviously fake (tsp-test-…-not-real), appear nowhere in the specs, and that none of the example tokens and ids in the vendor's documentation appears in the test files.

  2. Starts a local mock of the API that implements POST /oauth/token (form-encoded or JSON, as documented), Bearer auth, the zero-based paging (250 per page for projects, 3 per page, its own choice, where no size is documented), the documented filters, the leave bookings report, and the documented error bodies: { "Message": … } for the 400/404 examples, ResponseModel and an array of ValidationErrorModel where those are documented, and a 429 with Retry-After using the documented throttle message. The mock's list, report, token and error responses are validated against the response schemas; the token responses, which the documentation describes only by example (access_token, token_type, expiry_time; error, error_description), against schemas written from those examples. What the specs do not document is the mock's own choice and says so in its comments: the status of a rejected token request (401), the body of a 401 for a bad Bearer token, a 403 with MissingAdministratorPermission for a non-administrator, an empty array for an unknown user id.

  3. Starts the built server and drives it over stdio with the official MCP client, first as the Standard edition, then as the Recruitment edition: 37 checks in all. They cover the tool lists and annotations of both editions, that TSP_ALLOW_WRITES=true adds nothing (Standard), and the server instructions about the limits; the token request's exact form body and the token's reuse; paging to each end condition (a page shorter than 250, a shorter page after a full one, an empty page after a single page, a page repeating the previous one) and continuing from next_page; every documented filter sent under its documented name and date format (lastModified as yyyy-MM-ddTHH:mm:ss, ModifiedSince as yyyy-mm-dd HH:MM:SS, EndsAfter/StartsBefore as yyyy-MM-dd HH:mm); the report, leave report and invoice request bodies validated against ReportSettingsModel, LeaveReportSettings and InvoiceReportSettingsModel of the edition, and equal to the documented example's fields minus pay; report rows with a header row, as objects or as Key/Value pairs (injected responses that are deliberately off-schema), and a row with one value too many or too few refused rather than mislabelled; max_results capping projects (100 of the first page of 250) and invoices (100 of 150) with the number left out, and its hard maximum; the self-billing amounts withheld for a self-billing request even when the record's optional invoiceSelfBilling flag is absent; redaction by default (emails, phone numbers, NI numbers and postcodes in users, clients, projects, jobs, timesheet notes and any other report column, invoice line items and rate names, purchase orders, invoice external references, cost centre descriptions and leave names; a bare 8-digit reference and a hyphenated purchase order left alone) and the opt-in (users with their dates and accounting code, clients, projects, jobs, timesheet notes and pay columns, leave comments, self-billing amounts); bank, NI, tax, payroll, HR, custom field and audit data never returned even on request; card, IBAN, sort code and account numbers in free text removed even on request; the 429 retry with Retry-After in seconds, fractional seconds and HTTP-date form, the 2 s fallback, giving up after three attempts and at once above the 10 s cap (11 s gives up, exactly 10 s is waited out); the token endpoint's own 429, waited out on a session's first request and, three times over, reported with its own message; a 429 on a report POST retried; a 502 retried for GET and never for a POST; three 503s reported without the gateway HTML; a non-JSON 200 reported as an error with the excerpt redacted before it is cut; a 200 carrying an object where an array is documented reported as an error for cost centres, leave balances, users and the three report POSTs; the token refreshed after a 401 and before expiry, a second 401 after the refresh ending the call (one refresh, one retry), three concurrent calls on a fresh session sharing one token request, the secret and the cached token scrubbed from echoed messages, the token request not retried after a 503, the 403 message; invalid codes, dates, statuses and ranges rejected before any request; the request spacing of TSP_REQUESTS_PER_MINUTE; a call the client times out on sending nothing more (the retry and the next page are never sent); a paged walk stopping at the TSP_CALL_BUDGET_SECONDS budget with where to continue; startup refusing missing credentials and bad settings; wrong credentials giving an actionable message; and, last, that every request of the whole suite carried a Bearer token the mock issued (or the documented token form body), hit a method and path documented in its edition's spec with only query parameter names that operation documents and body keys its body schema declares, never touched /token, a write endpoint or a file endpoint, and never put the secret in a query string.

The suite takes about 30 seconds (the Retry-After boundary check waits the full 10 seconds).

Status

This is a working prototype. It has not yet been run against the live API, because it was built without a Timesheet Portal account. Everything below is taken from the published documentation and should be confirmed on a real account (the 30-day free trial may do, if it includes the API access tab):

  • The API host: whether tenant.api.timesheetportal.com is literally the shared host (as the general guidance writes it) or a placeholder for a per-account host (the spec's security definitions write yourhost.api.timesheetportal.com).

  • The token request end to end: that a form-encoded body is accepted (the guidance says JSON or form), that expiry_time comes back as documented (the server also reads expires_in and assumes an hour when neither is present), and the HTTP status of the documented invalid_client error (not documented; the server treats 400, 401 and 403 alike). Also the body and status the API answers for an expired or wrong Bearer token, and what a user without the System Administrator role gets (the mock answers 403 with MissingAdministratorPermission, an error code the spec lists).

  • Paging: the page size of /clients, /users and /jobs (undocumented), that page is zero-based on /projects too (the request models say zero-based; /projects only says "page size is 250 records"), and what a page past the end returns (the mock answers an empty array).

  • The timesheet report: that with reportFormat ValuesArray each row is an array of strings in the order of reportFields (the columns are mapped by position, and a row of any other length is refused as an error; a first row repeating the field names is dropped; rows as objects or Key/Value pairs are also read by name), that the fields and groupings of the documented examples work as sent, that All in timesheetStatusFilters includes every status, that pageIndex is zero-based, and the report's page size (undocumented; the tool asks the assistant to request the next page if more rows are expected).

  • Which filters are exact-match codes: employeeFilter, contractorFilter and chargeCodeGroupFilter have no description in the spec, and whether jobFilter takes a job code.

  • Date formats and zones: the parameters document several formats (above), and the EndsAfter/StartsBefore parameters of GET /jobs say yyyy-MM-dd HH:mm while its 400 description says yyyy-MM-dd HH:mm:ss. Dates are sent without a zone, as in the vendor's examples; which time zone the API reads them in is not documented.

  • The leave bookings report (POST /leavebookings/reports/bookings): that each row is an array of strings in the order of reportFields (mapped by position as above), that the chosen field names are accepted and hold what their names say (the enum has no descriptions), that leaving out statusFilters means every status (the spec says "Set to null for no status filtering"; this server never sends a null), how startDate and endDate select bookings that span them, whether the report is paged (nothing is documented), and whether it counts against the report limits.

  • POST /invoices: that invoiceReportType selects client or self-billing invoices and credit notes (its spec description reads "Deprecated - (only used by /invoices/ POST endpoint)"), that an empty invoiceStatusFilters returns every status as documented, that costCentreCode and chargeCodeGroupCodes filter as their names say, and whether the endpoint counts against the "Timesheet & Invoice Reports" limits.

  • GET /leavebookings/summary: the array-of-arrays response shape (flattened here) and the leaveYear format.

  • Which fields the live API fills: whether GET /users returns email, mobile and date of birth without includedFields, whether GET /clients needs include=address,billing for the address and billing fields, and whether GET /jobs returns the assigned contractors' names without an Include value (their codes are requested with Include=assignedcontractorcodes).

  • What GET /users?id= expects (the docs say "a single user specified by the id"; the server sends the value given, tested with a user code) and what it answers for an unknown id (only a 200 is documented; the mock answers an empty array).

  • The wording of the API's error and 429 messages and whether any echo request data; the server redacts contact details from them and scrubs its own secrets regardless.

  • How the per-account limits interact with other integrations on the same account; TSP_REQUESTS_PER_MINUTE can be lowered to leave room.

Going to production

This version runs locally over stdio with the account's own API credentials. For customers to connect from claude.ai or ChatGPT without handling credentials, the next step is a remote server (Streamable HTTP) behind OAuth, hosted by Timesheet Portal, that maps each signed-in user to their own permissions rather than to one System Administrator's, and then a listing in the Claude and ChatGPT connector directories. Timesheet approval and rejection can follow once an endpoint for it is documented, and expense reports once their response shape is documented; both need testing on a real account.

Licence

MIT. Built by Alexandru Dragoș (alexandru.dragos96@gmail.com) with an AI agent (Claude) working under his direction.

Available Tools

8 tools
list_clientsList clientsA
Read-onlyIdempotent

Clients (GET /clients) with code, name, category, cost centre, currency, payment terms, invoicing settings and notes. Filters sent as documented: id (a client code), costCentreCode, clientCategoryCode, ModifiedSince. Addresses, billing name and email, tax and company numbers only with include_contact_details (which also sends include=address,billing).

ParametersJSON Schema
NameRequiredDescriptionDefault
max_pagesNoMaximum number of pages to fetch in this call (each page is one request against the 30-per-minute limit)
start_pageNoZero-based page to start from (next_page from a previous call)
client_codeNoOnly this client (sent as id)
max_resultsNoMost clients to return in this call (default 100); the answer says how many were left out
category_codeNoSent as clientCategoryCode
modified_sinceNo
cost_centre_codeNo
include_contact_detailsNoInclude client addresses, billing name, billing email, tax number and company registration number, and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/non-destructive/openWorld, so the safety profile is covered. The description adds real behavioral context beyond that: the redaction rules that are lifted by include_contact_details (emails, phones, NI numbers, UK postcodes) and the note that bank/card numbers are always removed, plus the wire-level mapping of filters.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no filler, with the resource and returned fields front-loaded before the filter and include semantics. The second sentence is dense with sub-clauses but each clause carries distinct information, so nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter, no-required-argument list tool with no output schema, the description covers the returned fields, the filter set, and the one parameter with privacy implications, which is enough to call it correctly. Missing only an explicit note that pagination is driven by the schema's start_page/max_pages, which the schema already explains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 75%, so the schema documents most parameters, but the description adds value by mapping the friendly filter names to the wire parameters actually sent (id, costCentreCode, clientCategoryCode, ModifiedSince) and by explaining the compounded side effect of include_contact_details. It stops short of describing pagination interaction (start_page/max_pages), which the schema covers.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb+resource ('Clients (GET /clients)') and enumerates the returned fields (code, name, category, cost centre, currency, payment terms, invoicing settings, notes), so an agent immediately knows it is a client-listing read. It does not explicitly differentiate itself from the sibling list_* tools, but the resource noun is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It documents which filters exist and the conditional behavior of include_contact_details (which also sends include=address,billing), which implies when to reach for that flag. However, there is no explicit 'use this when' statement and no routing to sibling tools (e.g. list_projects for project data); the agent must infer context from the resource name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_cost_centresList cost centresB
Read-onlyIdempotent

Cost centres (GET /costcentres): code and description (contact details typed into a description are redacted). Filter sent as documented: Code.

ParametersJSON Schema
NameRequiredDescriptionDefault
cost_centre_codeNoOnly this cost centre (sent as Code)

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint, so the safety profile is covered. The description adds one genuinely useful behavioral fact beyond the annotations: contact details typed into a description are redacted. It does not mention pagination, result limits, or ordering, which is the main remaining gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single compact sentence with the resource and endpoint front-loaded. It is dense and slightly telegraphic ('Filter sent as documented: Code'), but nothing is wasted and it reads quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully names the returned fields (code, description) and flags redaction of contact details, which compensates for the missing return schema. For a one-param list tool it is nearly complete, though it omits any note on result size or pagination.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with a single optional parameter, so the schema already documents cost_centre_code as 'Only this cost centre (sent as Code)'. The description's 'Filter sent as documented: Code' largely restates the schema and adds no syntax or matching-semantics detail, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource (cost centres) and the backing endpoint GET /costcentres, plus the fields returned (code, description). The listing verb is carried by the tool name rather than the description, but the purpose is unambiguous and clearly distinct from siblings like list_projects or list_clients.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use or when-not-to-use guidance, and no alternatives are named. The one-param filter is mentioned but the description never says when an agent should reach for this tool versus another list_* sibling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_invoicesList invoicesA
Read-onlyIdempotent

Invoices dated in a range (POST /invoices, 'Get invoices'; a POST that changes nothing): number, status, dates, client, project, task, worker, currency, net, tax and total, and optionally the line items. invoice_type picks client invoices (default), client credit notes, self-billing invoices or self-billing credit notes (sent as invoiceReportType, whose spec description reads 'Deprecated - (only used by /invoices/ POST endpoint)'). Other filters sent as documented: invoiceStatusFilters (empty returns every status, Draft included), clientCode, costCentreCode, chargeCodeGroupCodes, exportedFilter, modifiedAfter. Self-billing invoices are contractor pay: their amounts only with include_contact_details (any invoice returned for a self-billing type is treated as self-billing, whatever its invoiceSelfBilling flag says). Probably counts against the report limits (24 per hour); the endpoint documents no paging, so at most max_results invoices are shown.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateYesInvoice date on or before, YYYY-MM-DD (at most 366 days after start_date)
exportedNoFilter on export status (exportedFilter)
statusesNoInvoice statuses to include; empty for all, Draft included
start_dateYesInvoice date on or after, YYYY-MM-DD
client_codeNo
max_resultsNoMost invoices to return in this call (default 100); the answer says how many were left out
invoice_typeNoClientInvoices
include_itemsNoInclude each invoice's line items
project_codesNoOnly these projects (branches), sent as chargeCodeGroupCodes
modified_afterNoOnly invoices modified after this date/time (modifiedAfter)
cost_centre_codeNo
include_contact_detailsNoInclude self-billing (contractor pay) amounts, and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent, and the description adds genuinely extra behavior: the 'POST that changes nothing' clarification, an approximate rate limit (24/hour), the absence of server-side paging plus the max_results cap, and the redaction rules for contact details with the always-removed bank/card numbers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One run-on paragraph mixing return fields, filter mappings, deprecation trivia, and rate limits; every claim is roughly relevant but there is filler ('sent as documented') and no structure to help an agent scan. Front-loading the date range is good, but readability suffers.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description must describe returns, and it does: the field list, the optional line items, and the note that the response reports how many invoices were omitted. Combined with the rate-limit and no-paging disclosures, an agent has everything needed to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 12 params and only 75% schema coverage, the description compensates well: invoice_type maps to the deprecated invoiceReportType, statuses-empty returns everything including Draft, project_codes is sent as chargeCodeGroupCodes, and the self-billing flag quirk (any invoice returned for a self-billing type is treated as self-billing) is spelled out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a precise verb+resource ('Invoices dated in a range ... POST that changes nothing') and enumerates the returned fields, so it is unmistakably the invoice-listing tool and not list_projects, list_clients, or list_timesheets.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states the date-range scoping requirement, explains that invoice_type selects client invoices vs credit notes vs self-billing, and ties include_contact_details to getting self-billing amounts. It does not name an alternative tool or state when-not-to-use, but the sibling set is a different resource so there is little ambiguity to resolve.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_leaveLeave bookings reportA
Read-onlyIdempotent

Leave bookings (holidays and other absences) in a date range (POST /leavebookings/reports/bookings, "Get leave bookings report"; a POST that changes nothing). Columns: EmployeeReference, EmployeeName, LeaveCategory, LeaveStatus, LeaveStartDate, LeaveEndDate, LeaveTotalWorkingDays, LeaveUnits, LeaveRequestDate, LeaveReviewedDate; LeaveComments only with include_contact_details. Filters sent as documented: statusFilters (omitted for every status), employeeFilter, employeeGroupFilter, modifiedAfterDate. The endpoint documents no paging; whether it counts against the report limits is not documented.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateYesLast date, YYYY-MM-DD (at most 366 days after start_date)
statusesNoLeave statuses to include (statusFilters); every status when omitted
start_dateYesFirst date, YYYY-MM-DD
max_resultsNoMost bookings to return in this call (default 100); the answer says how many were left out
employee_codeNoSent as employeeFilter
modified_afterNoOnly bookings modified after this date/time (modifiedAfterDate)
employee_group_codeNoSent as employeeGroupFilter
include_contact_detailsNoInclude the leave comments (LeaveComments, free text that can hold the reason for an absence), and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds value beyond annotations: it clarifies that the POST 'changes nothing' (consistent with readOnlyHint), discloses the effect of include_contact_details on redaction (email, phone, NI, postcodes; bank/card always removed), and honestly flags undocumented behaviors (no paging, unknown report-limit counting). The redaction detail is a genuine behavioral disclosure the annotations do not carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and columns, but the column enumeration is long and the trailing caveats ('whether it counts against the report limits is not documented') add length. Some of the filter-name mapping duplicates the schema. Adequate but not tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for an 8-param list tool with no output schema: it names the returned columns, notes the max_results 'answer says how many were left out', and transparently flags undocumented paging/limits. A note on ordering or the report-limit uncertainty resolution would round it out.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so parameters are already documented; description largely maps parameter names to API filter names (statusFilters, employeeFilter, etc.), which the schema descriptions also provide. It adds little syntax beyond what the schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Leave bookings (holidays and other absences) in a date range'), names the endpoint and operation, and enumerates the returned columns. Distinguishes it from sibling list_leave_balances by scope (bookings vs balances).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implies usage via the date-range scoping and the note that statusFilters covers 'every status' when omitted, but names no alternative tool and gives no explicit when-to-use/when-not. For an agent picking between list_leave and list_leave_balances, guidance is only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_leave_balancesLeave balancesB
Read-onlyIdempotent

Annual leave summary for every employee for one leave year (GET /leavebookings/summary): allowance, carried over, approved booked leave, non-deductible leave, manual adjustments and end-of-year balance, in the units the account records (hours or days). leave_year is any date in the wanted leave year (the API uses the leave year that date falls in); today when omitted.

ParametersJSON Schema
NameRequiredDescriptionDefault
leave_yearNoA date in the leave year, YYYY-MM-DD (defaults to today)
max_resultsNoMost employees to return in this call (default 100); the answer says how many were left out
employee_codeNoOnly this employee's row (filtered by this server; the endpoint has no employee filter)

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint:false and openWorldHint, covering the safety profile. The description adds genuine context beyond that: the underlying GET endpoint, that results are 'for every employee' (i.e. unfiltered by default), and that values are expressed in account-configured units (hours or days). It does not cover pagination/truncation behavior, so it stays at a solid 3.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded in the first clause, and the sentence earns its length by listing the exact metrics returned. It is slightly dense with the parenthetical endpoint, but there is little waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully enumerates the returned fields (allowance, carried over, booked, non-deductible, adjustments, balance) and their units, and all three optional parameters are documented. What is missing is any indication of default scope size or result-limit behavior, so it is not fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, which sets a baseline of 3, and the description adds real meaning on top: leave_year accepts any date within the desired leave year (the API maps that date to the containing leave year), which the schema only vaguely calls 'a date in the leave year'. It adds nothing for max_results or employee_code beyond the schema, but the leave_year clarification is a substantive addition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb+resource (annual leave summary for every employee, per leave year) and even enumerates the returned fields, so an agent knows exactly what this returns. It does not name or differentiate from the sibling list_leave, so it falls short of a 5 that would route the agent among alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use / when-not-to-use guidance and no reference to any sibling tool (e.g. list_leave). The only guidance is about leave_year resolution ('today when omitted'), which is parameter semantics rather than usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_projectsList projectsA
Read-onlyIdempotent

Projects (GET /projects, documented page size 250) with client, category, dates, charge budget, manager, cost centre and purchase order. Filters sent as documented: id (a project code), clientCode, categoryCode, lastModified. The pay budget, billing contact and address only with include_contact_details.

ParametersJSON Schema
NameRequiredDescriptionDefault
max_pagesNoMaximum number of pages to fetch in this call (each page is one request against the 30-per-minute limit)
start_pageNoZero-based page to start from (next_page from a previous call)
client_codeNo
max_resultsNoMost projects to return in this call (default 100); the answer says how many were left out
project_codeNoOnly this project (sent as id)
category_codeNo
modified_sinceNoOnly projects modified since this date or date and time
include_contact_detailsNoInclude the pay budget, billing contact name and email, and project address, and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, openWorld), so the description's value-add is its disclosure of the documented page size of 250 and, more importantly, the redaction behavior: contact/pay-budget/address fields are hidden and PII (emails, phones, NI numbers, postcodes) is redacted unless include_contact_details is set. That is meaningful behavioral context beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences with the resource, fields, filters, and the redaction condition front-loaded; almost no filler. Phrasing is terse to the point of being slightly clipped, but every clause carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully enumerates the returned fields and notes the page size and pagination limits. It covers filters and the conditional-contact behavior, which is sufficient for an 8-parameter, zero-required listing tool, though pagination mechanics (max_pages/start_page) are left entirely to the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 75%, so the schema already documents most parameters. The description adds the API-level wire names (id, clientCode, categoryCode, lastModified) and maps project_code to 'id', which helps cross-reference, but its include_contact_details note largely restates the schema's own detailed description. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Projects', GET /projects) and enumerates the fields returned (client, category, dates, charge budget, manager, cost centre, purchase order) plus the supported filters. It is unambiguous what the tool fetches. It does not explicitly differentiate itself from siblings, but those list entirely different resources, so confusion risk is low.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied rather than stated: the agent can infer this is the listing call for projects and that filters narrow results. There is no explicit when-to-use, when-not-to-use, or alternative routing guidance, nor any note on prerequisites. Adequate but leaves selection logic to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_timesheetsTimesheet (time and cost) reportA
Read-onlyIdempotent

Time recorded on timesheets for a date range (POST /reports/timesheets, the documented "time and cost report"; a POST that changes nothing), summed per task and employee for each period of time_grouping. Columns are the documented example's: Date, Status, CompanyName, CompanyReference, ChargeCodeGroup, ChargeCodeGroupReference, ChargeCode, ChargeCodeDesc, EmployeeReference, EmployeeName, ApproverName, EntryQuantity, RateCode, RateName, Units, ChargeRate, TotalCharge, TimesheetUpdateSequence; PayRate, TotalPay only with include_contact_details. Filters sent as documented: timesheetStatusFilters, clientFilter, costCentreFilter, employeeGroupFilter, employeeFilter, chargeCodeGroupFilter, taskCodeFilter, approvedDateFilterStart/End, modifiedAfterDate. Each call counts against the documented limit of 24 report requests and 500 records read per hour: keep ranges narrow. One page per call (pageIndex); the page size is not documented. Every value is redacted like free text, and a row whose length does not match the requested columns is refused rather than mislabelled.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateYesLast date, YYYY-MM-DD (at most 366 days after start_date)
statusesNoTimesheet statuses to include (default All)
task_codeNoSent as taskCodeFilter
page_indexNoPage of the report (pageIndex, zero-based)
start_dateYesFirst date, YYYY-MM-DD
approved_toNoOnly time approved before this date/time (approvedDateFilterEnd)
client_codeNoSent as clientFilter
max_resultsNoMost rows to return in this call (default 100); the answer says how many were left out
project_codeNoSent as chargeCodeGroupFilter
approved_fromNoOnly time approved since this date/time (approvedDateFilterStart)
employee_codeNoSent as employeeFilter
time_groupingNoTime grouping of the rows (reportTimeGrouping)Weekly
modified_afterNoOnly records modified after this date/time (modifiedAfterDate)
cost_centre_codeNoSent as costCentreFilter
employee_group_codeNoSent as employeeGroupFilter
include_contact_detailsNoInclude the pay columns (PayRate, TotalPay), and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: clarifies that the POST 'changes nothing' (reinforcing readOnly/idempotent), discloses the documented rate limits (24 report requests and 500 records read per hour), pagination behavior (one page per call, page size undocumented), blanket redaction of values, and that malformed rows are refused rather than mislabelled. The rate limits and redaction/no-op-row rules are exactly the kind of context structured fields cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the purpose and endpoint before the column list and constraints, and given 16 parameters and no output schema the length is largely justified. It is dense and slightly repetitious in its 'the documented ...' phrasing, but each sentence carries real information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by enumerating the returned columns, explaining the pay-column dependency on include_contact_details, and documenting pagination and truncation reporting ('the answer says how many were left out'). An agent has everything needed to call and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every parameter is already documented, including the filter name mappings (taskCodeFilter, clientFilter, etc.). The description restates the filter set and the include_contact_details pay-column rule but adds little syntax or nuance beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource (time recorded on timesheets for a date range), names the underlying endpoint (POST /reports/timesheets) and identifies it as the documented 'time and cost report'. It also clarifies the aggregation ('summed per task and employee for each period of time_grouping'), which distinguishes it from the sibling entity-listing tools like list_projects and list_users.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear situational context: this is the reporting endpoint, and the caller should 'keep ranges narrow' because of the hourly request/record limits. It does not explicitly name when to choose this over a sibling report/list tool, so it falls just short of the 5 bar for named alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_usersList usersA
Read-onlyIdempotent

Users (GET /users): code, name, job title, role, employment type, group, home cost centre and line manager. Filters sent as documented: id (a single user), groupCode, ModifiedSince. Join and leave dates, the accounting code (which the spec says usually holds the payroll number), email, mobile, date of birth, gender, address and a limited-company contractor's company details only with include_contact_details. Bank details, national insurance and tax numbers, the payroll details in employmentDetails and HR personal details are never returned (includedFields is never sent).

ParametersJSON Schema
NameRequiredDescriptionDefault
user_idNoOnly this user (sent as id; the docs say 'Return a single user specified by the id')
max_pagesNoMaximum number of pages to fetch in this call (each page is one request against the 30-per-minute limit)
group_codeNoSent as groupCode
start_pageNoZero-based page to start from (next_page from a previous call)
max_resultsNoMost users to return in this call (default 100); the answer says how many were left out
modified_sinceNo
include_contact_detailsNoInclude join and leave dates, accounting (payroll) code, email, mobile, date of birth, gender, title, home address and limited-company details, and stop redacting email addresses, phone numbers, national insurance numbers and UK postcodes typed into names and other text (bank and card numbers are always removed)

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/openWorld/non-destructive, but the description adds real behavioral context absent from structured fields: bank details, NI/tax numbers, payroll and HR personal details are never returned, and include_contact_details controls redaction of text fields. Rate-limit context exists but is carried in the schema, not the description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the resource and its fields, then the filter contract, then the explicit exclusions. Dense but every sentence carries distinct information; nothing is padded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 7 params and no output schema, the description compensates by enumerating returned fields and what is withheld, which is what an agent needs to interpret results. Pagination/leftover-count semantics live in the schema, so the combined picture is nearly complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 86%, so the schema does most of the work. The description still adds value by mapping caller concepts to wire names ('Filters sent as documented: id ... groupCode, ModifiedSince') and explaining the include_contact_details redaction scope.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the resource (Users / GET /users) and enumerates the returned fields, so an agent knows exactly what this tool retrieves. Sibling list_* tools are differentiated only implicitly by resource name, which is distinct enough but not called out.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It names the supported filters (id, groupCode, ModifiedSince) and explains when include_contact_details matters, implying usage. However, it never states when to prefer this over the sibling list_* tools or any exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 8 tool updatesv0.1.0
    • First observedlist_clients
    • First observedlist_cost_centres
    • First observedlist_invoices
    • First observedlist_leave
    • First observedlist_leave_balances
    • First observedlist_projects
    • First observedlist_timesheets
    • First observedlist_users

TDQS

A4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool wraps a distinct resource (projects, clients, users, cost centres, timesheets, invoices, leave balances, leave bookings) with a clear resource+action pairing. The only near-neighbour pair, list_leave_balances (aggregate annual summary) vs list_leave (individual bookings), is clearly differentiated by descriptions and parameters.

Naming Consistency5/5

Uniform list_<plural_noun> snake_case pattern across all eight tools, with consistent British spelling for 'cost_centres'. No mixing of verb styles or casing conventions.

Tool Count5/5

Eight tools is well-scoped for a read/reporting-oriented timesheet API, and each covers a genuinely distinct reporting need. No redundant or filler tools.

Completeness4/5

Broad read coverage of the portal's core entities and reports, but reference data used by filters (charge codes, task codes, rate codes) has no listing tool, and the surface is entirely read-only so no create/update/approve workflows exist. These are workable gaps for a reporting server but leave some dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only access to company data across PostgreSQL, MongoDB Atlas, and flat files through MCP tools, allowing AI assistants to query and retrieve information via natural language.
    -
  • A
    license
    A
    quality
    C
    maintenance
    It enables MCP clients such as Claude and ChatGPT to read an estate agency's Dezrez Rezi CRM data, including properties and their timelines, people, groups, and offers. It is read-only and applies privacy redactions by default.
    10
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables Claude, ChatGPT and other MCP clients to read an Amiqus ID account—clients, onboarding records and steps, check results, templates, case status counts and webhooks—and, when writes are enabled, create records.
    9
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Lets MCP clients such as Claude and ChatGPT read a rota and time-and-attendance account, exposing venues, groups, shifts, absences and absence types, time entries, venue events and staff names through read-only tools that never return pay data.
    MIT