Skip to main content
Glama

keka-mcp

A local MCP (Model Context Protocol) server that wraps Keka HR's PSA timesheet and HRIS APIs, read-only, over stdio. Runs in Docker on your machine and connects to Claude Desktop (or any MCP client) as a local stdio server.

This is scaffolding only: seven read-only tools, OAuth token handling, and pagination. No reporting/analysis logic and no write/POST endpoints - that's a later phase, driven by prompting Claude against this server.

Tools

Tool

Wraps

Notes

get_timesheet_entries

GET /api/v1/psa/timeentries

from, to, employeeIds, projectIds, pageSize. Auto-paginates. Max 60-day range (confirmed against the live sandbox - see below).

get_project_time_entries

GET /api/v1/psa/projects/{id}/timeentries

projectId (required), from, to, employeeIds, pageSize. Auto-paginates. Max 60-day range (confirmed against the live sandbox - see below).

get_employees

GET /api/v1/hris/employees

pageSize. Auto-paginates.

get_projects

GET /api/v1/psa/projects

pageSize. Auto-paginates.

get_clients

GET /api/v1/psa/clients

pageSize. Auto-paginates. Use to map a project's clientId to a readable client/customer name - see "Discovery: how a project maps to its client" below.

get_leaves

GET /api/v1/time/leaverequests

from, to, employeeIds, pageSize. Auto-paginates. Max 60-day range, same as the timesheet tools. status (0-4) is preserved raw, no label - see the tools table note below.

get_holidays

GET /api/v1/time/holidayscalendar/{calendarId}/holidays

calendarId, calendarYear, pageSize. Auto-paginates. Keka scopes holidays per calendar, not org-wide - if calendarId is omitted, this tool first looks up every calendar (GET /api/v1/time/holidayscalendar) and fetches + combines holidays from all of them, tagging each with calendarId/calendarName. Most tenants have exactly one calendar.

All seven auto-paginate internally (looping pageNumber until Keka reports no more pages) and return the fully combined result set - the MCP client never has to page itself. pageSize only controls the chunk size of each request to Keka (capped at 200); it does not limit the total number of results returned.

Related MCP server: HRIS MCP Connector

Setup

cp .env.example .env
# edit .env with your real client_id / client_secret / api_key / base URL
npm install
npm run build
npm test        # unit tests
npm start        # run directly with Node, without Docker

Required environment variables

See .env.example for the full list and descriptions:

  • KEKA_CLIENT_ID, KEKA_CLIENT_SECRET, KEKA_API_KEY - from Keka's Global admin settings -> Integrations & Automations -> API access -> API key.

  • KEKA_BASE_URL - your company's data API host, e.g. https://yourcompany.keka.com.

  • KEKA_TOKEN_URL - Keka's OAuth login host, e.g. https://login.keka.com/connect/token for production or https://login.kekademo.com/connect/token for the demo/sandbox environment. This is a different host from KEKA_BASE_URL and isn't derivable from it - see "Open questions" below.

Missing any of these causes a clean fatal error at startup (by design - this is the one class of failure allowed to stop the process). Every other error (bad API response, network failure, an unhandled exception inside a tool handler) is caught and returned to the caller as a normal MCP tool error; the server process itself keeps running.

Auth

src/auth.ts implements Keka's API-key OAuth exchange (docs):

  1. POSTs grant_type=kekaapi&scope=kekaapi&client_id=...&client_secret=...&api_key=... (form-urlencoded) to KEKA_TOKEN_URL.

  2. Caches the returned access_token and its expiry in memory, refreshing ~60s before it actually expires.

  3. If a refresh_token comes back, it's cached and used against the same token endpoint with grant_type=refresh_token (docs) on renewal, instead of re-running the full API-key exchange. If the refresh call fails (e.g. the refresh token was revoked), it falls back to a full API-key exchange automatically. If no refresh_token is ever returned (Keka's documented example response for this grant type doesn't include one), the API-key exchange is simply re-run on expiry.

  4. TokenManager.getValidToken() is the single entry point every tool call goes through; concurrent calls during a refresh are coalesced into one outstanding token request.

Docker

docker build -t keka-mcp .
docker run --env-file .env keka-mcp

Single-stage, pinned base image (node:22.12.0-bookworm-slim), TypeScript compiled at build time, npm prune --omit=dev after the build so no dev-only dependencies ship in the final image, no secrets baked in (.env is excluded via .dockerignore - only --env-file at docker run time supplies credentials).

Important: docker run --env-file .env keka-mcp (no -i) is fine as a smoke test - it starts, connects to stdio, logs its ready message, and exits cleanly when stdin closes. But an actual MCP client (Claude Desktop included) needs -i so Docker attaches the container's stdin/stdout to the client's pipes - without it, Docker never forwards stdio and the client can't talk to the server at all. See the Claude Desktop config below.

Claude Desktop configuration

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "keka": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "--env-file", "/absolute/path/to/keka-mcp/.env",
        "keka-mcp"
      ]
    }
  }
}

Restart Claude Desktop after editing the config. Use an absolute path to .env since Claude Desktop does not run this command from the project directory.

Testing

npm test

Unit tests (src/__tests__/, vitest) cover, with no network/Docker involved:

  • Pagination (pagination.test.ts) - a mocked 3-page response combines correctly into one result set, a single-page response doesn't over-fetch, and an empty page stops the loop even if totalPages implies more. The same three cases are repeated against Client-, LeaveRequest-, and Holiday-shaped rows (the shapes get_clients, get_leaves, and get_holidays use) to cover those tools' pagination specifically, since they go through the exact same paginateAll helper as every other tool.

  • Date-range validation (dateRange.test.ts) - accepts ranges up to and including MAX_DATE_RANGE_DAYS (60), rejects longer ranges with a message naming the limit and the dates given, rejects from after to, and skips validation when either side is omitted (matching Keka's own default-window behavior).

  • Token caching/expiry (auth.test.ts) - reuses a cached token while valid, re-runs the API-key exchange on expiry when no refresh_token was issued, uses the refresh endpoint instead when one was, and falls back to a full exchange if the refresh call itself fails.

scripts/mcp-smoke-test.mjs is a small standalone script (not part of the shipped server) that drives the built Docker image as a real MCP client over stdio - used for the manual verification below and reusable for future smoke-testing. It's written specifically against placeholder/dummy credentials (it asserts every data call comes back as a clean auth error) - that's intentional, it's exercising the failure path. It is not the script used for the real-sandbox run below.

Verified

Everything below was actually run in this environment, not assumed:

  • Unit tests: npm test - 24/24 passing (pagination x12 - generic + get_clients-shaped + get_leaves-shaped + get_holidays-shaped, date-range x7, token caching/refresh x5).

  • TypeScript build: npm run build completes with no errors.

  • Docker build: docker build -t keka-mcp . succeeds; final image has 0 known vulnerabilities after npm prune --omit=dev (dev-only vulnerabilities in vitest's esbuild/vite chain do not ship in the image).

  • Docker run / stays alive: docker run --env-file .env keka-mcp with a complete (dummy-valued) .env starts, logs its ready message, and exits 0 only when stdin closes - confirmed it does not crash or exit on its own while stdin stays open (docker ps showed Up during a 5s window with stdin held open).

  • Fatal startup path: running with required env vars missing produces a single clean FATAL error during startup log line naming the missing vars and exits 1, as designed - confirmed with KEKA_CLIENT_ID set alone.

  • MCP protocol handshake, over the real Docker container, via scripts/mcp-smoke-test.mjs (a real @modelcontextprotocol/sdk Client spawning docker run --rm -i --env-file .env keka-mcp, exactly as Claude Desktop would):

    • initialize succeeds and tools/list returns exactly the 7 expected tools: get_timesheet_entries, get_project_time_entries, get_employees, get_projects, get_clients, get_leaves, get_holidays.

    • Calling all 7 tools with dummy credentials against KEKA_TOKEN_URL=https://login.kekademo.com/connect/token: each returned a clean isError: true MCP result with message Keka token request failed (HTTP 400): invalid_client - i.e. Keka's real login host was reached and gave a real OAuth rejection (not a network failure or a 404), and the server surfaced it cleanly instead of crashing or leaking a stack trace to the client. get_clients, get_leaves, and get_holidays behave identically to the other four here - same auth flow via getValidToken(), same error shape.

    • Calling get_timesheet_entries with from=2026-01-01, to=2026-12-01 (334 days) returned isError: true with a message naming the limit and the dates given, without ever making a network call (the validation runs before the Keka client is touched). The smoke test script now asserts the message names the 60-day limit (updated to match the discovery below - it had been left asserting the original 90-day spec value, which no longer matched the code).

    • The container process was still alive and responsive after all 8 of the above calls, including the 7 induced failures - confirming a failed tool call does not take the process down.

Live sandbox run (real credentials, docker run --env-file .env keka-mcp via a real MCP client over stdio)

Once real values were in .env, I ran the tools end-to-end against the real sandbox tenant (never printing the credential values themselves) - five in the original run, get_leaves and get_holidays each added and verified in later sessions:

  • Auth: real API-key exchange succeeded against KEKA_TOKEN_URL, returned a token with expires_in: 86400, no refresh_token (matches the documented response shape and the "no refresh token yet" you told me up front).

  • get_timesheet_entries (from=2026-06-01, to=2026-06-15, no filters): isError: false, 958 records across 10 pages (default page size 100) - confirms pagination actually loops across a real multi-page dataset, not just the mocked unit test.

  • get_project_time_entries with a real project ID pulled from get_projects (NotificationHub, 046a855d-cedf-4aa4-bb73-fd2e9053296a): isError: false, 31 entries, all correctly scoped to that projectId.

  • get_projects: isError: false, 112 projects across 2 pages - a second confirmation of real multi-page pagination.

  • get_employees: initially isError: true with a real 403 from Keka ("You don't have privilege to access this resource.") - not a code bug, the API key lacked an HRIS/employees scope grant. After you added that privilege on the tenant, re-ran it: isError: false, 237 employees across 3 pages - a third confirmation of real multi-page pagination. (A later re-run for the get_clients addition below showed 238, i.e. one new employee was added to the tenant between sessions - not a bug.)

  • get_clients (added for this task): isError: false, 47 clients. At the default pageSize=100 all 47 fit on one page; re-ran with pageSize=10 to force multi-page pagination and got the same 47 records combined correctly across 5 pages, matching page-by-page against the raw API response with no duplicates or drops - a fourth confirmation of real multi-page pagination.

  • get_leaves (added later, wraps GET /time/leaverequests): isError: false on the first try, no extra privilege grant needed (unlike get_employees) - 294 leave requests across 3 pages, a fifth confirmation of real multi-page pagination.

  • get_holidays (added later, wraps GET /time/holidayscalendar + GET /time/holidayscalendar/{calendarId}/holidays): isError: false on the first try, no extra privilege grant needed - the tenant has exactly one calendar ("Holiday List"), and calling with no calendarId correctly auto-discovered it and returned 21 holidays for calendarYear=2026 in a single page, each tagged with the right calendarId/calendarName. Only one calendar meant this didn't exercise the multi-calendar combine path, but the per-calendar pagination is the same paginateAll proven five times over already, and the calendar lookup call and the holidays call are each single, unremarkable GETs - the last of the seven tools proven end-to-end.

  • Invalid-input handling:

    • Out-of-range dates (from=2026-01-01, to=2026-12-01, 334 days): isError: true with the 90-day message, and (confirmed via the tool call being logged before any Keka request would appear) rejected client-side without ever calling the API.

    • A bad/nonexistent employeeIds value: not an error - Keka treats it as a filter that matches nothing and returns totalRecords: 0 rather than a 4xx. That's Keka's own behavior (same as an unknown projectId, which also just returns 0 rows), not something this server should override.

  • Process resilience: the container stayed alive and kept answering correctly through all of the above, including the two induced failures.

Partially open: every one of the 958 timesheet entries pulled in that window had status: 2. That's consistent with 2 = Approved (the mapping this code assumes) but doesn't exercise the other five values - I have not seen a real 0/1/3/4/5 entry to confirm the full ordering. Still flagging this per "Open questions" below rather than calling it verified.

Discovery: the real date-range limit is 60 days, not 90

While probing for entries with a non-Approved status, a 90-day-wide request (from=2025-12-01, to=2026-02-28) came back from Keka itself with HTTP 400: "Total days should not exceed more than 60 days" - not a rate limit, not our own validation (a genuine content error from the API). That contradicts both the task spec and Keka's own public API reference, both of which say 90. MAX_DATE_RANGE_DAYS was changed to 60 to match what the live sandbox actually enforces (unit tests and tool descriptions updated to match, Docker image rebuilt) - you confirmed this rather than keeping the spec's original 90. If a different tenant/plan really does get 90 days, that's the one constant to change back.

Discovery: how a project maps to its client

Checked the real response shape of get_projects against the sandbox specifically to confirm this: each project object has a flat, top-level clientId field (a plain UUID string) - not a nested client.id or a client object. Example from the live sandbox:

// GET /api/v1/psa/projects
{ "id": "046a855d-...", "name": "NotificationHub", "clientId": "c0a7cc1a-be9b-4211-8b93-0cc1b004158a", ... }

That clientId is exactly the id field on the corresponding object returned by get_clients:

// GET /api/v1/psa/clients
{ "id": "c0a7cc1a-be9b-4211-8b93-0cc1b004158a", "name": "Technogise", "billingName": "Technogise", "code": "Technogise", ... }

Confirmed by fetching all 47 clients from the live sandbox and looking up NotificationHub's clientId (c0a7cc1a-...) by exact match against each client's id - it resolved to a real client ("Technogise"), not a miss. So the mapping for reporting is simply project.clientId === client.id, no nesting or extra lookup involved. get_clients objects also carry billingName, code, description, billingAddress, and clientContacts beyond the bare name, in case those are useful later.

Discovery: Keka enforces a 50 API-calls/minute quota

Repeated testing (pulling the 958-entry / 10-page dataset, the 112-project / 2-page dataset, the 237-employee / 3-page dataset, plus several probe calls) tripped Keka's own rate limiter: "API calls quota exceeded! maximum admitted 50 per 1m." This surfaces cleanly as an isError: true result (KekaApiError, labeled "Rate limited by Keka - back off and retry later") rather than a crash, so no code change was needed here - but it's worth knowing for the next phase: a single get_timesheet_entries call over a wide range can itself cost 5-10+ underlying HTTP calls once pagination kicks in, so a handful of broad tool calls in quick succession can exhaust the budget for the rest of that minute (and in this session, the quota stayed exhausted for several minutes rather than clearing after 60s, so the reset window may be longer than 1 minute in practice, or the quota may be shared with other integrations on the same tenant). Nothing to fix now, since it's out of scope for this task, but the reporting/analysis phase should batch and pace its calls with this in mind.

Open questions for you (flagging rather than guessing)

  • status field shape mismatch: the task described get_timesheet_entries's per-entry status as a string enum (UnSubmitted, Submitted, Approved, Rejected, InApprovalProcess, Invoiced). Keka's own API reference for /psa/timeentries documents status as an integer, 0-5, with no explicit label mapping given. The code preserves the raw integer from the API unmodified (as required) and adds a statusLabel field using the order you listed (0=UnSubmitted ... 5=Invoiced) as a best-effort guess. The live sandbox run confirmed 2 → Approved is at least plausible (958/958 entries in the tested window were status: 2, all approved-looking data), but every entry pulled had the same value, so the other five haven't been seen in practice. Confirm the full ordering with Keka (or point me at an entry with a different status) before relying on statusLabel.

  • KEKA_TOKEN_URL is a new required env var, not in your original list. Keka's docs show the OAuth token endpoint lives on a separate login.* host (e.g. login.keka.com, login.kekademo.com) from the company data API host in KEKA_BASE_URL - there's no way to derive one from the other, so it's a separate, explicit setting. Resolved: your sandbox uses the value now in .env, confirmed working against the live token exchange.

  • get_employees 403 - resolved. It was an HRIS/employees scope missing on the API key; you granted the privilege on the tenant and a re-run confirmed 237 employees across 3 pages. No code change was needed.

  • get_leaves's status field (0-4) has no label mapping at all - unlike timesheet status, you never gave an expected enum for leave requests, and Keka's own reference doesn't publish one either ("specific meanings not provided"). Unlike timeEntries.ts, leaves.ts deliberately does not add a guessed statusLabel - the raw integer is passed through as-is. If you know the mapping (something like Pending/Approved/Rejected/Cancelled plus one more), let me know and I'll add it the same way.

Project structure

keka-mcp/
  src/
    auth.ts            # OAuth token fetch + refresh (TokenManager)
    dateRange.ts        # date-range validation, shared by both timesheet tools
    kekaClient.ts        # HTTP wrapper: base URL, auth header, pagination helper
    tools/
      helpers.ts         # shared error handling / logging / JSON result wrapper
      timeEntries.ts      # get_timesheet_entries, get_project_time_entries
      employees.ts        # get_employees
      projects.ts         # get_projects
      clients.ts          # get_clients
      leaves.ts           # get_leaves
      holidays.ts          # get_holidays
    index.ts            # MCP server entrypoint, registers all tools
    __tests__/           # vitest unit tests
  scripts/
    mcp-smoke-test.mjs   # manual end-to-end stdio smoke test (see "Verified")
  Dockerfile
  .env.example
  package.json
  tsconfig.json

Available Tools

7 tools
get_clientsGet clientsA

Fetch all PSA clients (customers) from Keka. Automatically pages through the entire result set and returns it combined. Use this to map a project's clientId (from get_projects) to a readable client/customer name for reporting.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageSizeNoPage size used internally per request to Keka (max 200, default 100).

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral burden. It discloses an important non-obvious behavior: automatic paging through the entire result set and combining the results. It implies a non-mutating fetch, though it could add more about response shape or error behavior.

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

Conciseness5/5

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

Two sentences, front-loaded with the action and resource, followed by the key behavioral guarantee and a concrete use case. Every sentence earns its place and there is no filler.

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 a low-complexity tool with one optional parameter and no output schema, the description is largely sufficient: it explains behavior, scope, and purpose. It does not describe the return envelope or field names, but the mapping use case implies the relevant output and the missing details are not essential for invoking it.

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?

The single parameter pageSize is fully described in the schema (type, bounds, default), so the description adds no additional parameter meaning. The description focuses on overall behavior rather than pageSize, which is acceptable given 100% schema coverage.

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 states a specific verb ('Fetch all') and resource ('PSA clients/customers from Keka'), and it explains what the result is used for. The resource name clearly distinguishes it from sibling get_* tools.

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 gives a concrete use case: mapping a project's clientId from get_projects to a readable client/customer name for reporting. It does not explicitly state when not to use this tool or mention alternatives, but the sibling tools are distinct resources, so the context is clear.

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

get_employeesGet employeesA

Fetch all employees from Keka HRIS. Automatically pages through the entire result set and returns it combined. Use this to map employee IDs found in timesheet data to names, job titles, and employment status.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageSizeNoPage size used internally per request to Keka (max 200, default 100).

TDQS

A4.3/5.0
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It meaningfully reveals that the tool automatically pages through the entire result set and combines it, which is non-obvious behavior. It does not discuss authentication, rate limits, or exact response shape, but for a simple read-only fetch the pagination disclosure is the most important behavioral trait.

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

Conciseness5/5

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

Two tight sentences deliver the core behavior, pagination detail, and a concrete use case without any filler. The action and resource are front-loaded, and every clause earns its place.

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?

For a single-optional-parameter fetch tool with no output schema, the description is complete enough: it explains what is fetched, how pagination is handled, and what the results should be used for. The stated mapping use case also implies the important return fields (employee ID, name, job title, employment status), so an agent can call the tool and use its result appropriately.

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?

There is only one optional parameter, pageSize, and the input schema already describes it completely, including default and maximum values. The description's mention of automatic paging complements the schema, but it does not add new parameter-level meaning beyond what the schema already covers.

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 opens with a specific verb and resource: 'Fetch all employees from Keka HRIS.' It also clarifies scope (all employees, no filters) and introduces the intended mapping purpose, which clearly distinguishes it from sibling tools that handle timesheets, projects, clients, leaves, and holidays.

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?

The description gives an explicit use case: 'Use this to map employee IDs found in timesheet data to names, job titles, and employment status.' It does not explicitly list when not to use it or name alternative tools, but the sibling set is sufficiently distinct and the stated use case is concrete enough for an agent to select it correctly.

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

get_holidaysGet holidaysA

Fetch holidays from Keka's holiday calendar(s). Keka scopes holidays per calendar rather than org-wide, so if 'calendarId' is omitted this tool first looks up every holiday calendar and then fetches and combines holidays from all of them (most tenants only have one). Each returned holiday is tagged with the calendarId/calendarName it came from. Automatically pages through every result set involved and returns it combined.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageSizeNoPage size used internally per request to Keka (max 200, default 100).
calendarIdNoRestrict to a single holiday calendar ID. If omitted, holidays from every calendar are fetched and combined.
calendarYearNoFilter holidays to a specific calendar year (e.g. 2026). Omit to get all years Keka has data for.

TDQS

A4.6/5.0
Behavior5/5

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

With no annotations present, the description carries the full burden and does so exceptionally well. It discloses that multiple calendars are queried and combined, that results are tagged with origin calendar metadata, and that pagination is handled automatically.

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

Conciseness5/5

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

Three dense sentences with no filler. The purpose is front-loaded, and each subsequent sentence adds behavior an agent needs to know before calling the tool.

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?

The description covers scoping, combining, tagging, and pagination, which is strong for a read-style lookup tool with no output schema. It could go further by naming the returned holiday fields, but an agent can invoke the tool correctly with the information given.

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 100%, but the description adds meaningful behavioral context beyond the schema, particularly for calendarId omission and automatic pagination. This lifts it above the baseline without fully detailing every parameter's edge behavior.

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 opens with a specific verb and resource: 'Fetch holidays from Keka's holiday calendar(s).' It clearly differentiates itself from sibling tools by explaining the per-calendar scoping behavior rather than an org-wide holiday lookup.

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?

The description provides clear context for when to use the tool and how optional parameters change its behavior, especially the calendarId omission path. It does not explicitly name alternatives like get_leaves, so it stops short of full exclusion guidance.

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

get_leavesGet leave requestsA

Fetch leave requests across the organization from Keka for a date range. Automatically pages through the entire result set and returns it combined. The 'from'..'to' range cannot exceed 60 days (validated before calling Keka; defaults to the last 30 days if both are omitted). Each request's numeric status field (0-4) is preserved as returned by the API - Keka's docs don't publish a label mapping for it, so no guessed label is added (unlike timesheet status).

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date (e.g. 2026-06-30). Defaults to today if omitted.
fromNoStart date (e.g. 2026-06-01). Defaults to 30 days before 'to' if omitted.
pageSizeNoPage size used internally per request to Keka (max 200, default 100).
employeeIdsNoComma-separated Keka employee IDs to filter by.

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations provided, the description carries the full behavioral burden, and it delivers substantial detail: automatic paging, combined results, a 60-day range limit validated before calling Keka, default date behavior, and preservation of the numeric status field without guessed labels. It does not cover auth, rate limits, or errors, but it is far more transparent than a minimal fetch description.

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

Conciseness5/5

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

Three sentences, each earning its place: the first states the core purpose, the second covers paging behavior, and the third explains date constraints and status handling. The description is front-loaded and contains no filler.

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 a read-oriented list tool with four optional parameters and full schema coverage, the description provides the key call-time context: paging, range validation, defaults, and status semantics. The lack of an output schema is not fully compensated because the agent does not learn what fields each leave request contains, but the operational details needed to invoke it correctly are present.

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 100%, so the baseline is 3, but the description adds meaningful extra semantics by disclosing the 60-day maximum range and the automatic paging behavior that affects how from/to and pageSize are used. It does not add much for employeeIds, but the schema already documents those parameters fully.

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 uses a specific verb ('Fetch') plus a clear resource ('leave requests across the organization from Keka for a date range'). This immediately distinguishes it from sibling tools like get_timesheet_entries, get_employees, and get_holidays, and it does not merely restate the title.

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?

The description clearly implies this is the tool for org-wide leave requests over a date range, but it never explicitly states when to prefer it over alternatives or when not to use it. There is no explicit alternative routing or exclusion guidance, so usage is mostly inferred from the resource name.

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

get_projectsGet projectsA

Fetch all PSA projects from Keka. Automatically pages through the entire result set and returns it combined. Use this to map project IDs found in timesheet data to project names.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageSizeNoPage size used internally per request to Keka (max 200, default 100).

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations provided, the description carries the behavioral disclosure burden. It discloses a notable behavior: 'Automatically pages through the entire result set and returns it combined.' This goes beyond the schema and helps the agent understand performance and aggregation semantics. It does not describe error behavior or exact return shape, but for a simple fetch tool this is a reasonable and valuable level of transparency.

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

Conciseness5/5

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

The description is three short, purposeful sentences. It opens with the action and resource, then states the pagination behavior, then provides the primary use case. There is no filler or redundant restatement of the title.

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?

Given that this is a low-complexity tool with one optional parameter and no output schema, the description covers the essential context: what is fetched, the automatic pagination behavior, and the expected use case. It does not spell out the exact return fields, but it implies project IDs and names through the mapping use case, which is sufficient for an agent to call it correctly.

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?

The schema already has 100% coverage, including a clear description of pageSize: 'Page size used internally per request to Keka (max 200, default 100).' The tool description itself does not add meaning beyond the schema, so the baseline score of 3 is appropriate.

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 states a specific verb and resource: 'Fetch all PSA projects from Keka.' It further clarifies the scope ('all PSA projects') and gives an explicit use case: mapping project IDs from timesheet data to project names. This clearly distinguishes it from sibling tools like get_timesheet_entries or get_clients by resource and intent.

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?

The description gives clear contextual guidance: 'Use this to map project IDs found in timesheet data to project names.' This tells an agent when the tool is appropriate, but it does not explicitly name alternatives or state when not to use it. The instruction is clear context without exclusions, so it falls just short of a 5.

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

get_project_time_entriesGet project timesheet entriesA

Fetch PSA timesheet entries for a single project from Keka. Automatically pages through the entire result set and returns it combined. The 'from'..'to' range cannot exceed 60 days (validated before calling Keka).

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date. Defaults to today if omitted.
fromNoStart date. Defaults to 30 days before 'to' if omitted.
pageSizeNoPage size used internally per request to Keka (max 200, default 100).
projectIdYesKeka project ID to fetch time entries for (required).
employeeIdsNoComma-separated Keka employee IDs to filter by.

TDQS

A4.2/5.0
Behavior4/5

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

With no annotations, the description carries the behavioral disclosure burden and does it well: it reveals automatic pagination through the full result set, combined returns, and a 60-day range validation before calling Keka. This goes beyond what the schema alone conveys.

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

Conciseness5/5

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

Two sentences with no filler. The core action is front-loaded, and the behavioral notes (pagination, range validation) are placed right after, each earning its place.

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 a fetch tool with five well-documented parameters, the description covers the key operational behaviors: scope, pagination, and validation. No output schema exists, but the description does not need to enumerate fields since it clearly states the returned data is the combined result set.

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 the schema already documents all five parameters clearly. The description adds useful contextual details (auto-pagination, combined result, date-range limit) but does not add per-parameter meaning beyond the schema baseline.

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 ('Fetch') and resource ('PSA timesheet entries for a single project from Keka'), which clearly distinguishes it from sibling tools like get_timesheet_entries. The single-project scope is explicit and unambiguous.

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?

The description clearly implies when to use this tool: when fetching entries for a single project. It provides context about pagination and date constraints but does not explicitly name alternative tools or state when not to use it.

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

get_timesheet_entriesGet timesheet entriesA

Fetch PSA timesheet entries from Keka across employees/projects for a date range. Automatically pages through the entire result set and returns it combined - callers never need to loop on pageNumber themselves. The 'from'..'to' range cannot exceed 60 days (validated before calling Keka). Each entry's numeric status field is preserved as returned by the API, with a best-effort statusLabel (UnSubmitted/Submitted/Approved/Rejected/InApprovalProcess/Invoiced) added alongside it.

ParametersJSON Schema
NameRequiredDescriptionDefault
toNoEnd date (e.g. 2026-06-30). Defaults to today if omitted.
fromNoStart date (e.g. 2026-06-01). Defaults to 30 days before 'to' if omitted.
pageSizeNoPage size used internally per request to Keka (max 200, default 100). Does not limit total results - all pages are fetched and combined.
projectIdsNoComma-separated Keka project IDs to filter by.
employeeIdsNoComma-separated Keka employee IDs to filter by.

TDQS

A4.3/5.0
Behavior5/5

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

With no annotations, the description carries the full transparency burden and does so well. It explicitly discloses auto-paging through the entire result set, that callers never need to loop on pageNumber, the 60-day range validation, and the best-effort statusLabel enrichment alongside the preserved numeric status. These are meaningful behaviors beyond what the schema or name implies.

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

Conciseness5/5

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

Three sentences, all dense with useful information: purpose first, then auto-paging behavior, then status field handling. No filler or repetition of schema details.

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?

The tool has moderate complexity, no output schema, and no annotations, but the description covers the critical behavioral aspects an agent needs: scope, paging, date-range limit, and status field semantics. The only notable gap is the lack of explicit differentiation from get_project_time_entries, but it is not essential for making a correct call.

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?

The input schema already covers 100% of parameters with descriptions, so the baseline is 3. The description adds real value by clarifying pageSize 'does not limit total results' and by noting the 60-day range validation that constrains from/to, which supplements the schema's date defaults.

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 opens with a specific verb+resource: 'Fetch PSA timesheet entries from Keka across employees/projects for a date range.' This clearly names the tool's domain and scope. It does not explicitly contrast itself with sibling get_project_time_entries, but the 'PSA timesheet entries across employees/projects' phrasing provides enough operational context to be distinct.

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?

The description gives clear usage context: call this for PSA timesheet entries over a date range, with automatic paging handled internally. It does not state when not to use it or name alternatives, but the scope is specific enough that an agent can infer the appropriate situation.

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. 7 tool updatesv0.1.0
    • First observedget_clients
    • First observedget_employees
    • First observedget_holidays
    • First observedget_leaves
    • First observedget_project_time_entries
    • First observedget_projects
    • First observedget_timesheet_entries

TDQS

A4.3/5.0

Scored across 7 tools

Disambiguation4/5

Each tool targets a distinct resource (timesheet entries, project time entries, employees, projects, clients, leaves, holidays). The two timesheet tools could be confused since both fetch timesheet entries, but the descriptions clearly distinguish by scope (all vs. single project).

Naming Consistency5/5

All tools follow a consistent get_<resource> pattern with snake_case, making the set predictable and easy to navigate. The naming convention is uniform across all seven tools.

Tool Count5/5

Seven tools is well-scoped for a Keka integration covering PSA and HRIS data. Each tool serves a distinct data-fetching purpose, and the count feels appropriate for the server's apparent read-only reporting scope.

Completeness3/5

The server covers the main read-only data needs (timesheets, projects, clients, employees, leaves, holidays) but lacks write operations and some potentially useful lookups like individual employee details or project-specific leaves. For a reporting-focused server this is reasonable, but there are notable gaps if broader HRIS/PSA workflows are expected.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables querying HR data like recent hires, employee details, departments, and PTO balances through natural language in an MCP client.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables managing freee HR operations like employee data, attendance, leave, and approvals through natural language in MCP-compatible clients such as Claude Desktop.
    1
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables natural-language-based employee leave management including leave balance checks, leave applications, approvals, and history retrieval through an MCP-compatible client.
    -