# MCP & API Integration Guide Pyunto Time Management speaks **MCP (Model Context Protocol)**. Connect it to an AI client — Claude Code, Claude Desktop, Codex CLI, Cursor — and you can just ask: > "How many hours went to each project this week?" > "Write my daily report from yesterday's work." > "Log the last 90 minutes to the Design task on project Alpha." - User guide: [Guide](./guide.en.md) - 日本語: [MCP・API 連携ガイド](./mcp.ja.md) --- ## 1. What it exposes | Tool | What it does | Scope needed | |---|---|---| | `whoami` | The connected account, key name, granted scopes, decryption status | — | | `list_projects` | Projects with id, name, colour and role | `projects:read` | | `list_tasks` | Gantt tasks and subtasks, filterable by project or by date | `projects:read` | | `list_time_blocks` | Individual blocks in a range, with times and memos | `blocks:read` | | `time_summary` | Total hours in a range grouped **by project, task or day** | `record:read` | | `log_time` | Record worked time (**writes**) | `blocks:write` | | `update_time_block` | **Change** a recorded block's times, memo or task | `blocks:write` | | `delete_time_block` | **Delete one** recorded block | `blocks:delete` | | `list_deleted_time_blocks` | Deleted blocks that can **still be restored** | `blocks:read` | | `restore_time_block` | **Put back** a deleted block | `blocks:write` | Ranges are capped at 92 days. The writing tools change a real calendar, so their descriptions instruct the assistant to confirm the details with you first. > ⚠ **Deletion is reversible for 14 days.** A deleted block is hidden, not > destroyed: `restore_time_block` brings it back, attachments included. After > 14 days it is **purged permanently**. `delete_time_block`'s description requires > the assistant to show you the block it is about to delete — date, times, > project, task, memo — and get an explicit yes for that specific block, and to > confirm one at a time rather than clearing a range. No bulk delete is exposed, > because the blast radius of an automated caller getting a range wrong is > unbounded. --- ## 2. Mint an API key 1. Sign in at 2. **Settings → Account → API keys** 3. Name it (e.g. `claude-code`) and pick **scopes**: | Use | Scopes | |---|---| | Read-only (reports, summaries) | `projects:read` `blocks:read` `record:read` | | Also log and edit time | the above + `blocks:write` | | Also delete blocks | the above + `blocks:delete` (**deliberate grant** — see below) | | Also read names and memos (E2EE accounts) | the above + `keys:read` | 4. The key (`ptm_…`) is shown **exactly once**. Copy it somewhere safe. --- ## 3. E2EE and decrypting names Pyunto Time Management is end-to-end encrypted, so **the server does not hold your names or memos**. That means: | Data | Without key material | With key material | |---|---|---| | Dates, times, durations, totals | ✅ readable | ✅ readable | | Project names, task names, memos | ❌ empty | ✅ readable | To read names, give the key the `keys:read` scope **and** pass your account password to the MCP server. The password is used **only on your machine**. What the server hands over is the private key *wrapped under your password*; the password unwraps it locally and is never sent anywhere. If you'd rather not put it in a config file, write it to a file and point `PYUNTO_TM_PASSWORD_FILE` at it. If you don't need names, skip the password. --- ## 4. Install **There is nothing to install up front.** The MCP server is published on npm and the `npx` command in each client's config fetches and runs it. All you need is **Node.js 18+**. ### Checking whether you have it Open a terminal and run `node -v`. - **macOS**: Applications → Utilities → **Terminal** - **Windows**: Start menu → type "PowerShell" ```bash node -v ``` - `v18.0.0` or newer and you're set - `command not found` / `not recognized` means it isn't installed. Get the **LTS** installer from and run it — it's a normal installer. Then **open a new terminal** and check `node -v` again > **Claude Desktop users:** prefer the installer from nodejs.org. Node installed > via Homebrew or nvm is often invisible to Claude Desktop — see the note under > "6. Claude Desktop" below. The MCP server is open source: - Repository: (MIT) - npm: `@pyunto/tm-mcp` (the server), `@pyunto/tm-sdk` (the REST client) Since you may hand it your password, **being able to read it matters**. The claim that "the password never leaves your machine" is verifiable in `mcp/src/index.ts` and `sdk/src/crypto.ts` in that repository. Pyunto Time Management itself (the server side) is closed source. What's public is the client side: the code that calls the public API and decrypts your own data locally.
Building from source instead ```bash git clone https://github.com/pyunto/tm-integrations cd tm-integrations && npm install && npm run build ``` Then use `node /absolute/path/tm-integrations/mcp/dist/cli.js` wherever the instructions below say `npx -y @pyunto/tm-mcp`.
--- ## 5. Claude Code From your project directory: ```bash claude mcp add pyunto-tm \ --env PYUNTO_TM_API_KEY=ptm_your_key \ --env PYUNTO_TM_PASSWORD=your_password \ -- npx -y @pyunto/tm-mcp ``` Drop `--env PYUNTO_TM_PASSWORD=…` if you don't need decrypted names. Add `--scope user` to make it available in every project. To edit config directly, add this to `~/.claude.json` (or the project's `.mcp.json`): ```json { "mcpServers": { "pyunto-tm": { "command": "npx", "args": ["-y", "@pyunto/tm-mcp"], "env": { "PYUNTO_TM_API_KEY": "ptm_your_key", "PYUNTO_TM_PASSWORD": "your_password" } } } } ``` Verify: ```bash claude mcp list # pyunto-tm should read ✓ connected ``` Inside a session, `/mcp` shows the same. Then just talk: ``` > Break down this week's hours by project > Log 14:00–15:30 today to Alpha / Design ``` --- ## 6. Claude Desktop Open the config file: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` Add the same JSON as above and **restart Claude Desktop**. `pyunto-tm` appears under the tools icon in the composer. ### If the server won't start: `npx` not found Claude Desktop is a desktop app, so it launches MCP servers with a **minimal PATH that is not your shell's**. If `npx` works in your terminal but Claude Desktop reports that the server failed to start, that's why — it happens with Node installed through Homebrew (`/opt/homebrew/bin`) or nvm (`~/.nvm/…`). The fix is to put the **absolute path to `npx`** in the config. Find it first: ```bash which npx # e.g. /usr/local/bin/npx, /opt/homebrew/bin/npx ``` Then use that as `command`: ```json { "mcpServers": { "pyunto-tm": { "command": "/opt/homebrew/bin/npx", "args": ["-y", "@pyunto/tm-mcp"], "env": { "PYUNTO_TM_API_KEY": "ptm_your_key" } } } } ``` With nvm, `npx` depends on nvm's version-switching machinery, so even an absolute path may not work. Reinstalling Node from the **LTS installer** at is the reliable fix — it lands in `/usr/local/bin`, which Claude Desktop can see. --- ## 7. Codex CLI Add to `~/.codex/config.toml`: ```toml [mcp_servers.pyunto_tm] command = "npx" args = ["-y", "@pyunto/tm-mcp"] env = { PYUNTO_TM_API_KEY = "ptm_your_key", PYUNTO_TM_PASSWORD = "your_password" } ``` Or from the command line: ```bash codex mcp add pyunto-tm \ --env PYUNTO_TM_API_KEY=ptm_your_key \ -- npx -y @pyunto/tm-mcp codex mcp list ``` > TOML keys can't contain hyphens, hence the `pyunto_tm` section name. --- ## 8. Other clients (Cursor, …) Any client that speaks MCP over stdio takes the same three ingredients: | Field | Value | |---|---| | Command | `npx` | | Args | `-y` `@pyunto/tm-mcp` | | Env | `PYUNTO_TM_API_KEY` (required), `PYUNTO_TM_PASSWORD` (optional), `PYUNTO_TM_BASE_URL` (self-hosted only) | Cursor reads `.cursor/mcp.json` in the same shape as Claude Desktop. --- ## 9. Environment variables | Variable | Required | Default | Purpose | |---|---|---|---| | `PYUNTO_TM_API_KEY` | ✅ | — | The `ptm_…` key | | `PYUNTO_TM_BASE_URL` | | `https://tm.pyunto.com` | Change only when self-hosting | | `PYUNTO_TM_PASSWORD` | | none | Decrypts names and memos; used locally only | | `PYUNTO_TM_PASSWORD_FILE` | | none | Path to a file holding the password, to keep it out of config | --- ## 10. Troubleshooting Start by asking the assistant to **run `whoami`** — it reports the server, key name, granted scopes and whether decryption is working, all at once. | Symptom | Cause and fix | |---|---| | `the API key is missing, wrong or revoked` | Wrong `PYUNTO_TM_API_KEY`, or revoked in settings. Mint a new one | | `API key lacks scope 'blocks:write'` | Recreate the key with the scopes you need | | Names come back empty | E2EE. Add the `keys:read` scope and set `PYUNTO_TM_PASSWORD` | | `unlock failed` | Wrong password, or the key lacks `keys:read` | | `rate limited` | 240 requests per minute per key. Wait and retry | | Doesn't appear in the client | Check `node -v` is 18+ and that `npx -y @pyunto/tm-mcp` runs on its own. If it works in your terminal but fails only in Claude Desktop, it's the PATH (see the note under "6. Claude Desktop"). Behind a proxy that can't reach the npm registry, build from source as above | | `node` / `npx` not found | Node.js isn't installed. Run the LTS installer from , then open a new terminal | The server's own diagnostics go to your MCP client's log (`/mcp` in Claude Code, "MCP logs" in Claude Desktop). --- ## 11. Using the REST API directly You don't need MCP to integrate: ```bash curl -H "Authorization: Bearer ptm_your_key" \ "https://tm.pyunto.com/api/v1/service-record?date_from=2026-08-01&date_to=2026-08-31" ``` | Endpoint | Returns | Scope | |---|---|---| | `GET /api/v1/me` | Key info | — | | `GET /api/v1/projects` | Projects | `projects:read` | | `GET /api/v1/tasks` | Tasks | `projects:read` | | `GET /api/v1/blocks` | Blocks (`date_from` / `date_to`) | `blocks:read` | | `POST /api/v1/blocks` | Create a block | `blocks:write` | | `PATCH /api/v1/blocks/{id}` | Change a block | `blocks:write` | | `DELETE /api/v1/blocks/{id}` | Delete one block (restorable for 14 days) | `blocks:delete` | | `GET /api/v1/blocks/deleted` | Deleted blocks still within the window | `blocks:read` | | `POST /api/v1/blocks/{id}/restore` | Undo a delete | `blocks:write` | | `GET /api/v1/service-record` | Per-day, per-project aggregates | `record:read` | | `GET /api/v1/keys` | Password-wrapped key material | `keys:read` | For TypeScript there's an SDK that handles decryption for you (MIT, source in the repository above): ```bash npm install @pyunto/tm-sdk ``` ```ts import { PyuntoTM } from "@pyunto/tm-sdk"; const tm = new PyuntoTM({ apiKey: process.env.PTM_KEY! }); await tm.unlock(process.env.PTM_PASSWORD!); // only to read E2EE names const rows = await tm.serviceRecord("2026-08-01", "2026-08-31"); ``` --- ## 12. Staying safe - Treat an API key like a password. Don't commit config files that contain one - Use a separate key per integration with the **narrowest scopes** that work — a read-only use has no business holding `blocks:write` - **Revoke** keys you no longer use, from the settings screen - `log_time` and `update_time_block` write real data. Keep a confirmation step before automated runs - **Grant `blocks:delete` only when you need it.** It is separate from `blocks:write` precisely so a key minted to "log my time" cannot also destroy it. A delete can be undone for 14 days; after that it cannot --- *Last updated: August 2026 — published at .*