Kimai Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Kimai Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A comprehensive Model Context Protocol (MCP) server for integrating with the Kimai time-tracking API. This server allows AI assistants like Claude to efficiently interact with Kimai instances to manage time tracking, projects, activities, customers, users, teams, absences, and more.
Speaks MCP protocol revision 2026-07-28 and every earlier revision (MCP Python SDK 2.x), tracked against Kimai 2.66. Older Kimai instances keep working; features that need a specific Kimai version are marked below.
# Install from PyPI
pip install kimai-mcp
# Run with your Kimai credentials
kimai-mcp --kimai-url=https://your-kimai.com --kimai-token=your-token
# Or use the interactive setup wizard
kimai-mcp --setupFor enterprise/team environments: Deploy the server once and let all users connect remotely!
#### Server Types
| Server | Command | Best For |
|---|---|---|
| Local | kimai-mcp | Claude Desktop, single user, development |
| Streamable HTTP | kimai-mcp-streamable | Claude.ai Connectors (web/mobile), teams |
Removed in v2.16.0: the SSE server and itskimai-mcp-servercommand are gone. The SSE transport was dropped from the MCP specification in favour of Streamable HTTP, and this project's implementation had been non-functional and deprecated since v2.12.0. Usekimai-mcp-streamablefor remote access.
#### Quick Start with Docker (Streamable HTTP + OAuth)
Since v2.12.0 the Streamable HTTP server includes an OAuth 2.1 authorization server. Users authenticate with a user slug and a per-user auth_secret instead of a secret URL.
# 1. Generate a random slug and an auth_secret per user
python -c "import secrets; print(secrets.token_urlsafe(16))" # slug
python -c "import secrets; print(secrets.token_urlsafe(32))" # auth_secret
# 2. Create config file
mkdir config
cat > config/users.json << 'EOF'
{
"xK9mP2qW7vL4aB8c": {
"kimai_url": "https://your-kimai.com",
"kimai_token": "your-api-token",
"auth_secret": "long-random-oauth-login-secret"
}
}
EOF
# 3. Start server
docker-compose up -dSecurity Warning: Use random slugs, NOT usernames! The server warns at startup about low-entropy slugs (shorter than 16 characters or plain lowercase words). Slugs may only contain letters, digits,-and_.
#### Claude.ai Connectors Integration (OAuth)
The Streamable HTTP server works with Claude.ai custom connectors:
auth_secret for each user in users.json (see above) kimai-mcp-streamable \
--users-config ./config/users.json \
--public-url https://mcp.example.com \
--trusted-proxy 127.0.0.1 \
--oauth-state-file ./config/oauth_clients.json \
--disable-legacy-slugshttps://mcp.example.com/mcp (no slug in the URL)auth_secret.Access tokens are valid for 1 hour and refreshed automatically (refresh tokens up to 30 days). Tokens are kept in memory, so users must reconnect after a server restart.
Legacy endpoints: The previous per-user URLs /mcp/{slug} still work but are deprecated. Migrate to the OAuth endpoint /mcp and start the server with --disable-legacy-slugs.
#### OIDC federated login (optional)
Instead of the built-in slug + auth_secret form, the Streamable HTTP server can delegate user authentication to any standard OpenID Connect provider (Microsoft Entra ID / Azure AD, Keycloak, Auth0, Google, Okta, …). The server stays the OAuth 2.1 authorization server toward Claude.ai (same opaque tokens, DCR and PKCE); it only federates the login step: /authorize redirects to your OIDC provider, the id_token is verified (signature via JWKS, iss/aud/exp/nonce), and the resulting identity is mapped to a configured user.
kimai-mcp-streamable \
--users-config ./config/users.json \
--public-url https://mcp.example.com \
--auth-backend oidc \
--oidc-issuer https://login.microsoftonline.com/<tenant-id>/v2.0 \
--oidc-client-id <client-id>
# Confidential clients: set KIMAI_MCP_OIDC_CLIENT_SECRET (env preferred over the CLI flag).Map each OIDC identity to a Kimai user by adding the oidc_identity field (matched case-insensitively against --oidc-identity-claim, default email):
{
"x7Kp2mQ9wL4r": {
"kimai_url": "https://kimai.example.com",
"kimai_token": "api_token_for_alice",
"oidc_identity": "[email protected]"
}
}Register `<public-url>/oauth/oidc/callback` as the redirect URI at your OIDC provider. Requires the [server] extra (pip install "kimai-mcp[server]", which pulls in PyJWT[crypto]). The built-in slug login form is not exposed while the OIDC backend is active.
When mapping by email, the id_token must also assert email_verified: true, otherwise the email claim is ignored — so a provider that lets users self-assert an unverified address cannot impersonate a mapped user. For providers that do not emit email_verified but are trusted to only issue verified emails, pass --oidc-allow-unverified-email (or KIMAI_MCP_OIDC_ALLOW_UNVERIFIED_EMAIL=true).
#### Automatic onboarding (optional)
The mapping above still has to be maintained by hand, and each entry needs an API token that an administrator first created in Kimai's web UI. With --auto-provision, an identity that matches no configured user is instead resolved against Kimai's own user list and given its own personal API token at first sign-in; after that, signing in with the IdP is the only step a user ever performs.
kimai-mcp-streamable \
--users-config ./config/users.json \
--public-url https://mcp.example.com \
--auth-backend oidc \
--oidc-issuer https://login.microsoftonline.com/<tenant-id>/v2.0 \
--oidc-client-id <client-id> \
--auto-provision \
--provision-kimai-url https://kimai.example.com \
--disable-legacy-slugs
# Prefer the env var for the admin token: KIMAI_MCP_PROVISION_ADMIN_TOKENRequirements
POST /api/users/{id}/api-token behind Kimai's own permission check. Without it every first-time sign-in is rejected, and the server probes for the plugin at startup and says so once.--provision-admin-token) belonging to a user with api-token_other_profile (to mint the token) and view_user (to list users at all: GET /api/users carries its own permission check). ROLE_SUPER_ADMIN has both in Kimai's default role mapping. The server probes for each at startup rather than failing on the first sign-in.--auth-backend oidc. There is no verified identity to resolve without a federated login.How an identity is matched. Rules run from strongest to weakest and stop at the first one that matches. A rule matching more than one Kimai user aborts instead of guessing, because a wrong match would hand one employee another employee's token. --provision-match selects how far to go:
| Mode | Rules |
|---|---|
exact | Kimai email equals the identity; Kimai username equals the identity |
normalized (default) | …plus username equals the address local part, plus a folded comparison of username/alias/email that makes anna.vondorf and Anna von Dorf compare equal. The folded comparison needs an address of at least two name parts, so max@ is never matched against a colleague whose alias is Max or whose address is max@ on another mail domain |
fuzzy | …plus the name / given_name+family_name claims against the Kimai alias, plus single name parts (anna@ vs. anna.vondorf@) |
The fuzzy rules are heuristics; enable them only if you know the shape of your directory. The minted token is verified against /api/users/me and discarded if it resolves to a different user, but that guards against a wrong token, not a wrong match.
Restrict the domains. Every rule except the two exact ones compares the address local part, which says nothing about where the identity came from. If your issuer can assert more than one domain (Microsoft Entra's common/organizations endpoints, B2B guest accounts, Google without an hd claim, Auth0 or Okta social connections), then an outside account named [email protected] matches the Kimai user anna.vondorf as the single candidate, so the ambiguity guard never fires and that employee's personal API token is handed over. Set --provision-allowed-domains corp.example,corp.de to bound it. Leaving it unset is only safe with a single-tenant issuer, and the server logs a warning at startup to that effect.
Whatever prevents an onboarding (no match, ambiguous match, plugin missing, missing permission, Kimai unreachable), the response is the same generic "not authorized" page as before, with the actual reason in the server log only. The feature is off by default and cannot change behaviour for an existing deployment.
Persistence. Provisioned users live in memory by default, like the OAuth access and refresh tokens: after a restart the next sign-in re-provisions them, which is idempotent (tokens are replaced by name, so nothing piles up in the Kimai profile). Pass --provision-store FILE to keep them across restarts; that file holds Kimai API tokens in plaintext and is written with mode 0600. A hand-written users.json entry always wins over a stored one.
A stored token that Kimai stops accepting, because an admin deleted it or a second replica re-minted it under the same name, is detected and discarded: the session is dropped, the entry is removed from the store, and the user's next sign-in provisions them again. Running several replicas against one Kimai still means they re-mint each other's tokens (Kimai replaces tokens by name), which now costs a re-provisioning rather than a session stuck in permanent failure. Give each replica its own --provision-token-name to avoid it entirely.
Idle sessions. A provisioned user's session is released after an hour without requests and rebuilt on their next one. Without that, every directory member who ever signed in kept a Kimai connection pool and a session manager for the lifetime of the process.
Slugs. Provisioned users get a random slug of the same strength as the ones users.example.json tells you to generate, and no auth_secret, so the local login form cannot be used for them. The slug alone is a credential on the deprecated /mcp/{slug} routes, so run auto-provisioning with --disable-legacy-slugs; the server warns at startup when both are active.
📖 [See full deployment guide →](DEPLOYMENT.md)
Options for the local server (kimai-mcp):
| Option | Description |
|---|---|
--kimai-url URL | Kimai server URL (e.g., https://kimai.example.com) |
--kimai-token TOKEN | API authentication token from your Kimai user profile |
--kimai-user USER_ID | Deprecated — accepted but ignored (use the user_scope parameter of the tools instead) |
--ssl-verify VALUE | SSL verification: true (default), false, or path to CA certificate |
--setup | Interactive setup wizard for Claude Desktop configuration |
--help | Show help message and exit |
--version | Show version number and exit |
Options for the Streamable HTTP server (kimai-mcp-streamable):
| Option | Environment variable | Description |
|---|---|---|
--host HOST | — | Host to bind to (default: 0.0.0.0) |
--port PORT | — | Port to bind to (default: 8000) |
--users-config FILE | USERS_CONFIG_FILE | Path to users.json |
--public-url URL | KIMAI_MCP_PUBLIC_URL | Public base URL, used as OAuth issuer/resource URL (required behind a reverse proxy) |
--oauth-state-file FILE | KIMAI_MCP_OAUTH_STATE_FILE | JSON file to persist registered OAuth clients across restarts |
--disable-legacy-slugs | KIMAI_MCP_DISABLE_LEGACY_SLUGS | Disable the deprecated /mcp/{slug} endpoints |
--trusted-proxy IP | KIMAI_MCP_TRUSTED_PROXIES (comma-separated) | Reverse proxy IPs whose X-Forwarded-For/X-Real-IP headers are honored; may be given multiple times |
--rate-limit-rpm N | RATE_LIMIT_RPM | Maximum requests per minute per IP (default: 60, 0 to disable) |
--auth-backend {local,oidc} | KIMAI_MCP_AUTH_BACKEND | Login backend: local (built-in slug form, default) or oidc (federate to an external OIDC provider) |
--oidc-issuer URL | KIMAI_MCP_OIDC_ISSUER | OIDC issuer URL (required for --auth-backend oidc) |
--oidc-client-id ID | KIMAI_MCP_OIDC_CLIENT_ID | OIDC client ID (required for --auth-backend oidc) |
--oidc-client-secret SECRET | KIMAI_MCP_OIDC_CLIENT_SECRET | OIDC client secret (optional; public/PKCE-only if omitted — prefer the env var) |
--oidc-scopes SCOPES | KIMAI_MCP_OIDC_SCOPES | Requested scopes (default: openid email profile) |
--oidc-identity-claim CLAIM | KIMAI_MCP_OIDC_IDENTITY_CLAIM | id_token claim mapped to a user's oidc_identity (default: email) |
--oidc-discovery-url URL | KIMAI_MCP_OIDC_DISCOVERY_URL | Override the discovery URL (default: <issuer>/.well-known/openid-configuration) |
--oidc-allow-unverified-email | KIMAI_MCP_OIDC_ALLOW_UNVERIFIED_EMAIL | Accept the email claim without email_verified: true |
--auto-provision | KIMAI_MCP_AUTO_PROVISION | Onboard unknown OIDC identities automatically (requires --auth-backend oidc and the ApiTokenBundle plugin) |
--provision-kimai-url URL | KIMAI_MCP_PROVISION_KIMAI_URL | Kimai URL written into provisioned user configs (required for --auto-provision) |
--provision-admin-token TOKEN | KIMAI_MCP_PROVISION_ADMIN_TOKEN | Admin token used to mint per-user tokens; needs api-token_other_profile and view_user (prefer the env var) |
--provision-allowed-domains DOMAINS | KIMAI_MCP_PROVISION_ALLOWED_DOMAINS | Comma-separated mail domains that may be onboarded. Strongly recommended unless the issuer is single-tenant |
--provision-token-name NAME | KIMAI_MCP_PROVISION_TOKEN_NAME | Name of the created tokens as shown in the Kimai profile (default: Kimai MCP (auto)) |
--provision-match {exact,normalized,fuzzy} | KIMAI_MCP_PROVISION_MATCH | How far to go when matching an identity to a Kimai user (default: normalized) |
--provision-store FILE | KIMAI_MCP_PROVISION_STORE | Persist provisioned users across restarts (plaintext tokens, written 0600) |
--provision-ssl-verify VALUE | KIMAI_MCP_PROVISION_SSL_VERIFY | SSL verification for provisioning calls: true, false or a CA path |
entity) - Universal CRUD operations for projects, activities, customers, users, teams, tags, invoices, holidaystimesheet) - Complete timesheet management (list, create, update, delete, export, batch operations)timer) - Active timer operations (start, stop, restart, view active/recent, favorites list/add/remove with Kimai 2.66+)rate) - Rate management across all entity typesteam_access) - Team member and permission managementabsence) - Complete absence workflow (create, approve, reject, list, attendance, batch operations, auto-split)calendar) - Unified calendar data accessmeta) - Custom field management for customers, projects, activities, timesheets, and invoices (invoice meta requires Kimai 2.56+)user_current) - Current user informationanalyze_project_team) - Advanced project analyticsconfig) - Server configuration (timesheet settings, color codes, plugins, version info)comment) - Comments on projects and customers: list, create, delete, pin (requires Kimai 2.57+)rate/internalRate/fixedRate/hourlyRate are omitted per record without view_rate_own_timesheet / view_rate_other_timesheet, and the tool says so instead of printing a zerolockedUntil on projects (Kimai 2.66+): timesheets up to and including that day are read-only for everyone, admins included. Readable in every project listing, writable via entity type=project action=update; the error hints explain the resulting 403/400language and invoiceEmail from Kimai 2.63+). Listings can request filters.full for the address and VAT ID (Kimai 2.62+, needs the details_customer permission); contact and email are only returned for a single customer via action=get; the budget fields appear in listings too since Kimai 2.66, but only for records the token holds the budget resp. time permission foredit_invoice); delete invoices (Kimai 2.66+, permission delete_invoice)pip install kimai-mcp# Clone the repository
git clone https://github.com/glazperle/kimai_mcp.git
cd kimai_mcp
# Install in development mode
pip install -e ".[dev]"https://kimai.example.com)Add the Kimai MCP server to your Claude Desktop configuration file:
On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
On Windows: %APPDATA%\Claude\claude_desktop_config.json
Add the following to your Claude Desktop configuration:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp",
"args": [
"--kimai-url=https://your-kimai-instance.com",
"--kimai-token=your-api-token-here"
]
}
}
}Important Notes:
https://your-kimai-instance.com with your actual Kimai URLyour-api-token-here with your API token from Kimaikimai-mcp command is available after pip install kimai-mcpAlternative: If kimai-mcp is not in your PATH, use python -m kimai_mcp.server instead:
{
"mcpServers": {
"kimai": {
"command": "python",
"args": [
"-m", "kimai_mcp.server",
"--kimai-url=https://your-kimai-instance.com",
"--kimai-token=your-api-token-here"
]
}
}
}After saving the configuration file, restart Claude Desktop for the changes to take effect.
#### Method 1: Using a .env File (Recommended for Development) If you prefer using a .env file for configuration, create a .env file in your project directory:
# .env file in the kimai_mcp directory
KIMAI_URL=https://your-kimai-instance.com
KIMAI_API_TOKEN=your-api-token-here
KIMAI_SSL_VERIFY=true # or path to CA certificateThen use this Claude Desktop configuration:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp",
"cwd": "/path/to/your/kimai_mcp/directory"
}
}
}Important Notes for .env Configuration:
/path/to/your/kimai_mcp/directory with the actual path to your kimai_mcp directorycwd parameter ensures the .env file is found in the correct directoryExample Windows Path:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp",
"cwd": "C:/Users/YourName/Projects/kimai_mcp"
}
}
}#### Method 2: Using Environment Variables (System-wide) If you prefer system environment variables, you can set:
export KIMAI_URL="https://your-kimai-instance.com"
export KIMAI_API_TOKEN="your-api-token-here"Then use this Claude Desktop configuration:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp"
}
}
}#### List Timesheets
{
"tool": "timesheet",
"parameters": {
"action": "list",
"filters": {
"project": 17,
"user_scope": "self"
}
}
}list returns one page (page, default 1; size, default 50, at most 500) with Kimai's total and a hint for the next page. Each entry is one line with names instead of IDs (the user only when other users' records were asked for), and a further line only for what applies. The hours are Kimai's duration, which already excludes the break. For example, this request
{
"tool": "timesheet",
"parameters": {
"action": "list",
"filters": {"user_scope": "all", "begin": "2026-10-01T00:00:00", "size": 2}
}
}answers
Found 57 timesheets for all users, showing 1-2
More records: use 'page' 2 for the next ones.
#102 2026-10-20 13:00-15:00, 2.00 h, Acme / Website Relaunch (#7) / Support (#3), Ben (#8)
not billable, exported
tags: bug; meta: issue_tracker_id=CA-376
#101 2026-10-20 09:00-17:30, 8.00 h (break 30 min), Acme / Website Relaunch (#7) / Development (#12), Ana (#5) - Sprint planninglist and get print the non-empty timesheet meta fields of each entry. Kimai only serializes meta fields whose definition is marked visible (setIsVisible(true) in the plugin).
#### List Timesheets by Meta Field
{
"tool": "timesheet",
"parameters": {
"action": "list",
"filters": {
"user_scope": "all",
"begin": "2026-10-01T00:00:00",
"meta": {"name": "issue_tracker_id", "value": "CA-376"}
}
}
}The value must match exactly (case-insensitive, the field name too). Omit value to get every entry where the field is set, or pass "value": "" for entries where it is not. The filter is sent to Kimai as the search term issue_tracker_id:CA-376 to narrow the result; because Kimai matches that term as a substring (CA-376 also finds CA-3760), the MCP server fetches the whole result and keeps only exact matches, then shows the requested page. A value with a colon needs Kimai 2.35+ (older versions split the search word at every colon and find nothing). A value Kimai's term syntax cannot express (spaces, a leading !, a backslash) is narrowed to "field is set" on the server and matched on the MCP side. Kimai sends back only the meta rows that are stored and visible, so an entry saved before the field existed, or while it was hidden, arrives without it: it counts as a match for "value": "", is listed as Kimai's (substring) match when Kimai searched this value, and is left out when the value could not be sent; each case is stated in a note.
#### Create a Timesheet Entry
{
"tool": "timesheet",
"parameters": {
"action": "create",
"data": {
"project": 1,
"activity": 5,
"description": "Working on API integration",
"begin": "2024-08-03T09:00:00",
"end": "2024-08-03T10:30:00"
}
}
}#### Start a Timer
{
"tool": "timer",
"parameters": {
"action": "start",
"data": {
"project": 1,
"activity": 5,
"description": "Working on API integration"
}
}
}#### Stop a Timer
{
"tool": "timer",
"parameters": {
"action": "stop",
"id": 12345
}
}#### List Projects
{
"tool": "entity",
"parameters": {
"type": "project",
"action": "list",
"filters": {"customer": 1}
}
}Kimai lists only projects running today unless told otherwise. Add "ignore_dates": true to include finished and not yet started projects, or "begin" / "end" to list the projects running at any time in that period.
#### Get Project Details
{
"tool": "entity",
"parameters": {
"type": "project",
"action": "get",
"id": 17
}
}#### List Activities
{
"tool": "entity",
"parameters": {
"type": "activity",
"action": "list",
"filters": {"project": 17}
}
}#### List Users
{
"tool": "entity",
"parameters": {
"type": "user",
"action": "list"
}
}#### Create a Team
{
"tool": "entity",
"parameters": {
"type": "team",
"action": "create",
"data": {
"name": "Development Team",
"color": "#3498db",
"members": [{"user": 5, "teamlead": true}]
}
}
}#### Add Team Member
{
"tool": "team_access",
"parameters": {
"action": "add_member",
"team_id": 1,
"user_id": 5
}
}#### Create an Absence
{
"tool": "absence",
"parameters": {
"action": "create",
"data": {
"comment": "Vacation in the mountains",
"date": "2024-02-15",
"end": "2024-02-20",
"type": "holiday"
}
}
}#### List Absences
{
"tool": "absence",
"parameters": {
"action": "list",
"filters": {
"user": "5",
"status": "all"
}
}
}#### Check Attendance (Who is Present Today)
{
"tool": "absence",
"parameters": {
"action": "attendance",
"date": "2024-12-29"
}
}Returns a report showing present and absent employees with absence reasons.
Batch operations allow executing multiple API calls in parallel for efficient bulk processing.
#### Batch Delete Absences
{
"tool": "absence",
"parameters": {
"action": "batch_delete",
"ids": [1, 2, 3, 4, 5]
}
}#### Batch Approve Absences
{
"tool": "absence",
"parameters": {
"action": "batch_approve",
"ids": [10, 11, 12, 13]
}
}#### Batch Delete Timesheets
{
"tool": "timesheet",
"parameters": {
"action": "batch_delete",
"ids": [100, 101, 102, 103]
}
}#### Batch Export Timesheets
{
"tool": "timesheet",
"parameters": {
"action": "batch_export",
"ids": [200, 201, 202]
}
}#### Batch Delete Entities
{
"tool": "entity",
"parameters": {
"type": "project",
"action": "batch_delete",
"ids": [5, 6, 7]
}
}#### List Customer Rates
{
"tool": "rate",
"parameters": {
"entity": "customer",
"action": "list",
"entity_id": 1
}
}#### List Project Comments
{
"tool": "comment",
"parameters": {
"entity": "project",
"entity_id": 17,
"action": "list"
}
}#### Add a Pinned Customer Comment
{
"tool": "comment",
"parameters": {
"entity": "customer",
"entity_id": 5,
"action": "create",
"data": {
"message": "Billing contact changed, see email from 2026-06-01",
"pinned": true
}
}
}#### Get Current User
{
"tool": "user_current"
}#### Automatic Absence Splitting
The MCP automatically handles Kimai's limitations when creating absences:
Year-Boundary Splitting: Kimai doesn't allow absences spanning multiple years. The MCP automatically splits them.
{
"tool": "absence",
"parameters": {
"action": "create",
"data": {
"date": "2025-09-01",
"end": "2026-03-31",
"type": "parental",
"comment": "Parental leave"
}
}
}This automatically becomes two entries:
2025-09-01 to 2025-12-312026-01-01 to 2026-03-3130-Day Limit Splitting: Kimai limits absences to 30 days maximum. Longer absences are automatically split into 30-day chunks.
{
"tool": "absence",
"parameters": {
"action": "create",
"data": {
"date": "2025-09-01",
"end": "2025-11-29",
"type": "parental",
"comment": "Parental leave (90 days)"
}
}
}This automatically becomes three 30-day entries with output:
Created 3 absence(s) for parental
Period: 2025-09-01 to 2025-11-29 (90 days)
IDs: 123, 124, 125
(Automatically split due to Kimai limitations)#### Connection Problems
#### Permission Errors
#### Configuration Issues
kimai_mcp module#### SSL Certificate Errors (Self-Hosted Instances)
If you're running a self-hosted Kimai instance with a custom CA certificate (e.g., self-signed certificates), you may encounter this error:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chainSolution 1: Use the `--ssl-verify` CLI option
# Point to your CA certificate file
kimai-mcp --kimai-url=https://kimai.example.com --kimai-token=your-token --ssl-verify=/path/to/ca-bundle.crt
# Or disable verification (not recommended for production)
kimai-mcp --kimai-url=https://kimai.example.com --kimai-token=your-token --ssl-verify=falseSolution 2: Use environment variables
# Using httpx's built-in SSL environment variables
SSL_CERT_DIR=/etc/ssl/certs kimai-mcp --kimai-url=... --kimai-token=...
# Or using the KIMAI_SSL_VERIFY environment variable
KIMAI_SSL_VERIFY=/path/to/ca-bundle.crt kimai-mcp --kimai-url=... --kimai-token=...Claude Desktop configuration with custom certificates:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp",
"args": [
"--kimai-url=https://kimai.example.com",
"--kimai-token=your-token",
"--ssl-verify=/path/to/ca-bundle.crt"
]
}
}
}Or using the environment variable:
{
"mcpServers": {
"kimai": {
"command": "kimai-mcp",
"args": ["--kimai-url=...", "--kimai-token=..."],
"env": {
"KIMAI_SSL_VERIFY": "/path/to/ca-bundle.crt"
}
}
}
}For debugging, you can run the server directly:
# Using command line arguments
kimai-mcp --kimai-url=https://your-kimai.com --kimai-token=your-token
# Using .env file (make sure you're in the directory with the .env file)
kimai-mcp
# Alternative: using Python module execution
python -m kimai_mcp.server --kimai-url=https://your-kimai.com --kimai-token=your-tokenThe server includes comprehensive logging. Check the logs for detailed error information.
kimai_mcp/
├── src/
│ ├── kimai_mcp/
│ │ ├── __init__.py
│ │ ├── server.py # Local MCP server (stdio)
│ │ ├── streamable_http_server.py # Streamable HTTP server with OAuth (Claude.ai)
│ │ ├── oauth.py # Embedded OAuth 2.1 authorization server
│ │ ├── user_config.py # users.json / env multi-user configuration
│ │ ├── security.py # Rate limiting, security headers, enumeration protection
│ │ ├── client.py # Kimai API client
│ │ ├── models.py # Data models
│ │ └── tools/ # MCP tool implementations
│ │ ├── entity_manager.py
│ │ ├── timesheet_consolidated.py # timesheet + timer tools
│ │ ├── rate_manager.py
│ │ ├── team_access_manager.py
│ │ ├── absence_manager.py
│ │ ├── calendar_meta.py # calendar, meta, user_current tools
│ │ ├── comment_tool.py # project/customer comments
│ │ ├── project_analysis.py
│ │ ├── config_info.py
│ │ ├── user_discovery.py # shared user resolution helper
│ │ ├── batch_utils.py
│ │ ├── absence_analytics.py
│ │ └── timesheet_analytics.py
├── tests/
├── README.md
├── pyproject.toml
└── .gitignorepytest tests/ -vThis project is licensed under the MIT License - see the LICENSE file for details.
This MCP server is an independent integration tool that communicates with Kimai via its public API. It is not a derivative work of Kimai itself and can be freely used under the MIT license terms.
We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
pip install -e ".[dev]"pytest tests/ -vexamples/ directory~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.