# Kaiku (remote · mcp.kaiku.tech)

Issue tracker and wiki for teams and their agents: find, file and update issues, write pages.

- Trust score: 74/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-23

## Components

- remote · `{workspace}.kaiku.tech`: 0/100, [markdown](https://verifymcp.io/servers/tech-kaiku-kaiku/https-workspace-kaiku-tech-mcp.md), [page](https://verifymcp.io/servers/tech-kaiku-kaiku/https-workspace-kaiku-tech-mcp)
- remote · `mcp.kaiku.tech`: 74/100 (this document), [markdown](https://verifymcp.io/servers/tech-kaiku-kaiku/mcp.md), [page](https://verifymcp.io/servers/tech-kaiku-kaiku/mcp)

## Channel facts

- Endpoint: `https://mcp.kaiku.tech/mcp`
- Transports: `streamable-http`
- Auth: `required`
- Version: `1.1.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-23.

- **Endpoint Security**: 89/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation is enforced on tool calls, but the challenge carries no valid RFC 9728 metadata, so a client cannot discover where to get a token.
  - HTTPS is enforced; there's no plaintext access path.
  - The HSTS (Strict-Transport-Security) header is present.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 66/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 20317 tokens (~274/item across 74 items; 74 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 3/100
  - Stability observed for 1 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 12 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "admin_bank_transfer" implies "transfer" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 75 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Kaiku MCP server?

Kaiku is a hosted endpoint at https://mcp.kaiku.tech/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add --transport http tech-kaiku-kaiku 'https://mcp.kaiku.tech/mcp'
```

### Cursor

```json
{
  "mcpServers": {
    "tech-kaiku-kaiku": {
      "url": "https://mcp.kaiku.tech/mcp"
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "tech-kaiku-kaiku": {
      "type": "http",
      "url": "https://mcp.kaiku.tech/mcp"
    }
  }
}
```

### Codex

```toml
[mcp_servers.tech-kaiku-kaiku]
url = "https://mcp.kaiku.tech/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tech-kaiku-kaiku": {
      "type": "remote",
      "url": "https://mcp.kaiku.tech/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add tech-kaiku-kaiku --url 'https://mcp.kaiku.tech/mcp' --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  tech-kaiku-kaiku:
    url: "https://mcp.kaiku.tech/mcp"
```

### Netclaw

```json
{
  "McpServers": {
    "tech-kaiku-kaiku": {
      "Transport": "http",
      "Url": "https://mcp.kaiku.tech/mcp"
    }
  }
}
```

### Vellum

```bash
assistant mcp add tech-kaiku-kaiku -t streamable-http -u 'https://mcp.kaiku.tech/mcp'
```

### Other

```json
{
  "mcpServers": {
    "tech-kaiku-kaiku": {
      "type": "http",
      "url": "https://mcp.kaiku.tech/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-23 (score 74, 0)

- [security] The server rewrote its instructions, which are the text every model session reads
- [security] Tool “add_attachment” rewrote its description, which is the text the model reads
- [security] Tool “add_wiki_attachment” rewrote its description, which is the text the model reads
- [security] Tool “get_attachment” rewrote its description, which is the text the model reads
- [security] Tool “get_wiki_attachment” rewrote its description, which is the text the model reads
- [functional improvement] Stability: unverified → 0.03
- [functional] New tool “local_files_setup”

### 2026-09-22 (score 74)

First indexed and scored.

## MCP tools (74)

### `add_attachment` (~304 tokens)

Attach a file to an issue: pass its bytes as 'contentBase64', or its content as 'text' when the file is text. Attaching a filename the issue already has replaces that file rather than adding a second copy, so retrying this call cannot double it. A file on the person's own disk — a screenshot, a PDF — is better attached by path through the pm-files MCP server than typed out as base64; local_files_setup says how to connect it. Returns the attachment's download URL. Requires permission to edit issues in that project.

Input parameters:

- `contentBase64` (string|null): The file's bytes, base64-encoded (a 'data:…;base64,…' URL is accepted and unwrapped). Use 'text' instead when the file is text.
- `filename` (string, required): File name to store, e.g. 'report.pdf'. Any directory part is dropped.
- `key` (string, required): Issue key, e.g. 'UK-1'.
- `mimeType` (string|null): Content type to record for the listing, e.g. 'text/csv'. Ignored when the bytes identify themselves (a PNG is a PNG whatever you call it), and never used to serve the file.
- `text` (string|null): The file's content as text, stored as UTF-8 — for a log, CSV or Markdown file you are writing here. Alternative to 'contentBase64'; passing both is refused.

### `add_comment` (~350 tokens)

Add a comment to an issue, or reply to one of its comments ('replyTo' is a comment id from get_comments). A reply notifies the author of the comment it answers, whether or not they watch the issue — no mention needed — and they are told once even if also mentioned. Requires permission to comment in that project (read-only members cannot). Answers with 'notified' (who this actually reaches, the answered author included), 'answered' (for a reply: whose comment it answers, and 'notified' false with a 'reason' when they are not told) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land. Where the workspace has agents, [~agent:role] (e.g. [~agent:product]) calls one to answer in the thread on your behalf: 'agents' lists each call with its requestId and status, and an agent that will not come is in 'unresolvedMentions' with its reason (agent_unknown, agent_off, agent_plan, agent_budget, too_many).

Input parameters:

- `body` (string, required): Comment body. Markdown is kept as written, and carries $…$ / $$…$$ LaTeX formulas and [~username] mentions (the username, which list_users hands you ready-made — never the display name).
- `key` (string, required): Issue key, e.g. 'UK-1'.
- `replyTo` (integer|null): The comment this one answers, as returned by get_comments — on the same issue and not deleted.

### `add_wiki_attachment` (~437 tokens)

Attach a file to a wiki page, identified by 'pageId' or by 'spaceKey' + 'title': pass its bytes as 'contentBase64', or its content as 'text' when the file is text. Re-attaching a filename the page already has replaces its bytes and bumps its version, as the wiki does everywhere else, so retrying this call cannot leave two copies. A file on the person's own disk — a screenshot, a PDF — is better attached by path through the pm-files MCP server than typed out as base64; local_files_setup says how to connect it. An attached file is not *on* the page until the body references it — the answer hands you the Markdown to add with update_wiki_page. An image may also carry a dark-theme version, attached as the same name with '-dark' before the extension (diagram.svg + diagram-dark.svg); the body still references the plain name. Requires permission to edit the page.

Input parameters:

- `comment` (string|null): Short note about this upload, shown in the page's attachment list.
- `contentBase64` (string|null): The file's bytes, base64-encoded (a 'data:…;base64,…' URL is accepted and unwrapped). Use 'text' instead when the file is text.
- `filename` (string, required): File name to store, e.g. 'diagram.png'. Any directory part is dropped.
- `mimeType` (string|null): Content type to record for the listing, e.g. 'text/csv'. Ignored when the bytes identify themselves, and never used to serve the file.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `text` (string|null): The file's content as text, stored as UTF-8 — for a log, CSV or Markdown file you are writing here. Alternative to 'contentBase64'; passing both is refused.
- `title` (string|null): Exact page title, when identifying the page by title.

### `add_wiki_comment` (~314 tokens)

Comment on a wiki page, or reply to one of its comments ('replyTo' is a comment id from get_wiki_page). A reply notifies the author of the comment it answers, whether or not they watch the page — no mention needed — and they are told once even if also mentioned. Identify the page by 'pageId', or by 'spaceKey' + 'title'. Markdown is rendered; mention someone as [@Their Name](user:username), the wiki's own form. Requires permission to comment in that project. Answers with 'notified' (who this actually reaches, the answered author included), 'answered' (for a reply: whose comment it answers, and 'notified' false with a 'reason' when they are not told) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land.

Input parameters:

- `body` (string, required): Comment body as Markdown, which also carries $…$ / $$…$$ LaTeX formulas.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `replyTo` (integer|null): The comment this one answers, as returned by get_wiki_page.
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `admin_bank_transfer` (~143 tokens)

Administrators only: where a bank transfer is to be sent (PM-625) — beneficiary, account and its own currency, bank and SWIFT, the intermediary bank if there is one, the purpose line, how many days a wire is given, and who last changed all of it. 'offered' is the one flag that answers «may a customer pick a transfer right now»: it is switched on AND carrying everything a payment needs, which is not the same as 'enabled'. A bill already issued carries its own frozen copy of these details and is not changed by what this returns. Amounts are always USD, whatever the account's own currency is.

### `admin_errors_week` (~115 tokens)

Administrators only: the error collector's week — events and distinct errors per project against the week before, errors first seen that week with their release, the loudest by events and by people, and errors that came back after being resolved. The same numbers the weekly error report carries. A project the collector did not answer about has null counts and a reason, never zeros. Ask for an earlier week with weeksAgo.

Input parameters:

- `weeksAgo` (integer): 0 is the last full week, 1 the one before it.

### `admin_funnel` (~365 tokens)

Administrators only: the installation as a business over a period — registrations, how many of them became working companies, purchases and conversion by cohort, churn, and money. Money comes in two halves that must not be added together: 'stock' is what recurs every month at today's prices, 'revenue' is what actually arrived inside the period. Each carries its own currency. Figures come from the fleet's ledger, so 'machines' says how current each machine's copy is — a machine that is behind makes the totals older than they look, it does not make them wrong. 'activation' is how many of the companies registered in the period have had their first agent come in (the first request with a configured token), and how soon — within the hour, the day, the week. 'ai' is AI as money over the same period — what our key's calls cost at each provider, what companies were charged, the margin, the cost nobody was charged for and why, what has accrued and what 'ai' invoices still want — and is neither in 'stock' nor in 'revenue'. Its totals are null, never zero, while a machine has not reported its AI: 'ai.missing' names it and 'ai.reported' is the sum over the machines that did. Default period: the last 90 days.

Input parameters:

- `days` (integer|null): How many days back from today. Defaults to 90, at most 730. Ignored when 'from' and 'to' are given.
- `from` (string|null): Start of the period, as YYYY-MM-DD. Use with 'to'.
- `to` (string|null): End of the period, as YYYY-MM-DD, included. Use with 'from'.

### `admin_invoices` (~185 tokens)

Administrators only: what companies were asked to pay and what answered it — each invoice with its amount, its coin, whether it is paid and what is short, and the payments against it. Amounts are never added across coins: each row carries its own currency and they stay apart. The list is the fleet's ledger, so 'machines' says how current each machine's copy is. One invoice's delivery record belongs to the machine that took the payment and is not rolled up — the answer says so where it is missing rather than showing an empty list.

Input parameters:

- `limit` (integer): How many rows, newest first. Default 50, at most 100.
- `slug` (string|null): Only invoices of the company at this address.
- `status` (string|null): Only invoices in this state, e.g. 'open', 'paid', 'void'.

### `admin_landing_week` (~91 tokens)

Administrators only: the landing site's week — visits, where they came from, clicks on sign-up, and the registrations that followed. The same numbers the weekly report carries into the monitoring chat. Ask for an earlier week with weeksAgo. The rendered message is not returned, only the figures.

Input parameters:

- `weeksAgo` (integer): 0 is the last full week, 1 the one before it.

### `admin_load` (~130 tokens)

Administrators only: how loaded this machine is and what the language models cost. Load is this machine's own — database, streams, memory, processor, traffic and stored bytes, each as used against allowed, where an allowance is known. Model spend covers the assistant, search-by-sentence and translation over a rolling window, with totals and breakdowns by feature, by model and by day; costs are estimates from the prices recorded at the time. Who spent it is not returned here.

Input parameters:

- `days` (integer): Window for model spend, in days. Default 30; 0 means all time.

### `admin_overview` (~127 tokens)

Administrators only: the whole installation by the numbers — accounts, workspaces, projects, issues, comments and sprints, plus issues broken down by status and by project. Counted across every workspace, which is what makes it the installation's rather than one company's. A total is null, never zero, when a machine of the fleet did not answer: 'fleet' says which machines were asked and which are missing, and 'here' is always what this machine counted. The breakdowns are this machine's. Read a null as 'nobody knows', never as 'none'.

### `admin_set_bank_transfer` (~538 tokens)

Administrators only, and this one CHANGES things: state where bank transfers are to be sent. Send the WHOLE set every time — anything left out is cleared, not kept, because details half of one bank and half of another are money sent to a mixture of two. Read admin_bank_transfer first and repeat what you are not changing. Switching the method on is refused while anything a payment needs is missing (beneficiary, account, swift, and a purpose line carrying {invoice}), and the refusal names the field; switching it off is never refused. 'minimumUsd' is a floor under the METHOD and not under the debt: a bill below it is offered the other ways to pay, and what is owed does not change. The change is recorded against the calling administrator. Bills already issued keep the details they were issued with.

Input parameters:

- `account` (string|null): The account number or IBAN.
- `accountCurrency` (string|null): The account's OWN currency, e.g. 'AMD'. Bills stay in USD; this is what the receiving bank converts into.
- `bankName` (string|null): The bank's name and address.
- `beneficiary` (string|null): Who the money is sent to — the account holder's name as the bank has it.
- `daysToArrive` (integer|null): How many days a wire is given to arrive, 1 to 60. The period stays open that long. Left out, the stored number is kept (7 to start with).
- `enabled` (boolean, required): Whether this installation takes transfers. On is refused while a required field is missing; off never is.
- `intermediaryAccount` (string|null): Our account at the intermediary bank.
- `intermediaryBank` (string|null): The intermediary bank's name, where one is needed.
- `intermediarySwift` (string|null): The intermediary bank's SWIFT/BIC.
- `minimumUsd` (number|null): The smallest bill that may be paid by transfer, in USD — correspondent fees eat a small one. Left out there is NO floor, which is the cleared state and not zero; a customer under it is offered the ot…
- `note` (string|null): A line for the payer, e.g. which charges are whose. Shown with the details.
- `purpose` (string|null): What the payer must write as the purpose. Has to carry {invoice} — a transfer that does not name its bill arrives as a sum nobody can place. Defaults to 'Invoice {invoice}'.
- `swift` (string|null): The bank's SWIFT/BIC.

### `admin_workspace_journal` (~142 tokens)

Administrators only: what was done to one company and by whom — created, plan changed, period extended by hand, credited, suspended, resumed, erased — newest first, up to a hundred entries, each with the reason that was written down at the time. This is the answer to 'why is this company on a period nobody paid for'. The actor is our own operator, and is named because an entry without one answers nothing. A company living on another machine of the fleet is not here: the answer then names the machine to ask.

Input parameters:

- `slug` (string, required): The company's address — 'acme' in acme.kaiku.tech.

### `admin_workspaces` (~286 tokens)

Administrators only: every company on the installation — its plan, what state it is in (trial, active, over-limit, suspended…), when what it paid for runs out, how many days are left, what it is using against what it may use, and its balance. Ask with endingInDays to get only the ones running out that soon, which is how 'whose trial ends this week' is answered in one call. 'attention' is the console's own short list of rows somebody has to look at. A meter reading null means the machine holding that company was not asked, not that it is idle. Owners are left out unless withOwners is true, and an e-mail address is never returned.

Input parameters:

- `endingInDays` (integer|null): Only companies whose paid-for period ends within this many days. 0 means already run out.
- `slug` (string|null): Only the company at this address ('acme' in acme.kaiku.tech).
- `status` (string|null): Only companies in this state: 'trial', 'active', 'free', 'over-limit', 'expired', 'suspended'.
- `withOwners` (boolean): Include who owns each company — username and display name, never an e-mail. Off by default: this is a statistics surface, and a list of customers with names on it is a customer list.

### `answer_issue_question` (~195 tokens)

Answer a question that was put to you on an issue, by choosing one of its options. Only the person the question was addressed to may answer it; answering again with a different option changes the answer and says so in the thread. A question whose deadline already passed can still be answered — that replaces whatever the deadline took, and the change is recorded. The answer is written into the thread as an ordinary comment and the asker is told.

Input parameters:

- `note` (string|null): Words of your own beside the option — a qualification, not the answer itself ('Postgres, but only after the migration lands'). Ignored when the asker switched free text off; check 'allowsFreeText' on…
- `option` (integer, required): Which option to choose, counted from 0 in the order they were offered.
- `questionId` (integer, required): The question's id, as returned by list_issue_questions or ask_issue_question.

### `ask_issue_question` (~435 tokens)

Ask one person a question with pre-written answer options, in an issue's thread — the way to get a decision that does not get lost in the comment stream. The question appears as an ordinary comment (so every Jira-speaking reader sees it) and is tracked separately: the person you ask sees it counted in their Questions list until they answer. It does not wait for ever — give 'defaultOption' and the deadline takes it if nobody answers, so a blocked run can go on. Requires permission to comment in that project. NOTE: this is not the bgs 'ask_question' tool, which is the background-agent ledger — this one is a question to a person on a PM issue.

Input parameters:

- `allowFreeText` (boolean): Whether the answerer may add words of their own beside the option they choose. Defaults to true. Set false when the answer has to be machine-comparable and nothing else.
- `defaultOption` (integer|null): Which option to take if nobody answers before the deadline, counted from 0. Give one whenever there is a safe assumption — it is what lets you carry on rather than stall. Omit it when no option is sa…
- `key` (string, required): Issue key, e.g. 'UK-1'.
- `options` (array, required): The answer options, in the order to offer them: at least 2, at most 8, each a short label rather than a paragraph. Fewer than two is not a question with options — that is a comment.
- `question` (string, required): The question itself. Keep it to what the options answer.
- `to` (string, required): Who to ask: the username, which list_users hands you ready-made — never the display name. They must be able to see this project, and it cannot be you.
- `ttlHours` (integer|null): How long to wait, in hours. Omitted is 48 (two days). 0 means no deadline at all — the question waits until somebody answers it, which is rarely what an agent wants.

### `create_issue` (~312 tokens)

Create an issue in a project visible to this token. Returns the new issue key. Requires permission to create issues in that project. Answers with 'notified' (who this actually reaches) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land.

Input parameters:

- `assignee` (string|null): Assignee username or email. Omit to leave unassigned.
- `description` (string|null): Issue description. Markdown is kept as written, and carries $…$ / $$…$$ LaTeX formulas and [~username] mentions (the username, which list_users hands you ready-made — never the display name).
- `dueDate` (string|null): The day this is due, as YYYY-MM-DD. A calendar day, not a timestamp — do not send a time or a zone.
- `labels` (string|null): Labels to set, space or comma separated.
- `parentKey` (string|null): Parent issue key, to create this as a child/sub-task.
- `priority` (string|null): Priority name, e.g. 'High'.
- `projectKey` (string, required): Project key, e.g. 'UK'.
- `summary` (string, required): Issue summary (title).
- `type` (string|null): Issue type name, e.g. 'Task', 'Bug', 'Story', 'Epic'. Defaults to 'Task'.

### `create_project` (~426 tokens)

Administrators only: create a project — a new key, a starter board, and its own wiki space, led by you unless you name someone else. The key is yours to choose and is never invented for you: one already taken is refused, naming the project holding it, rather than quietly becoming KEY2 — the key shows in every issue key, link and commit, and renaming it later costs more than creating the project did. Calling it again with the same key and name returns that same project with created:false, so a retry after a lost answer cannot make a second project. Deleting one is not offered here: that is a decision a person makes looking at what is inside. Who may: an administrator of this workspace, or — for a subproject — the lead of the parent, since it hands over their own project's people.

Input parameters:

- `description` (string|null): What the project is for. Markdown.
- `key` (string, required): Project key, 2–10 characters: a letter followed by letters or digits, e.g. 'BGS'. Uppercased.
- `lead` (string|null): Username or email of the project lead. Defaults to you — a project with no lead is one nobody can configure: export, colour, the bgs connection and access grants all need a lead or an admin.
- `name` (string, required): Project name, e.g. 'Background agents'.
- `parentKey` (string|null): Key of the project this one is a subproject of. Everybody who may reach the parent may reach this project too, in the role they hold there — so add only the people the parent does not already have. L…
- `projectTypeKey` (string|null): Jira project type: 'software' (the default), 'business' or 'service_desk'.

### `create_project_rule` (~809 tokens)

Add a rule to a project: when an issue enters 'status', append 'template' to its description. A rule appends its checklist to an issue's description when the issue ENTERS the rule's state — from the board, the Jira API or update_issue alike. The block starts with a hidden marker line '[//]: # (pm-rule:<id>)'; while that line is in the description, entering the state again adds nothing (the journal says 'alreadyThere'). A [~username] in the template is a mention: that person is notified when the checklist lands. A checklist that would make the description too long is not added and the journal says 'tooLong'; the move itself still happens. A rule with action 'agent' is a DUTY instead: it says which issues may be handed to an agent role (agentRole, e.g. 'product') and what the agent may do with them. Its 'template' is then the INSTRUCTION — what doing such an issue means in this project — and nothing is appended anywhere. A duty may have no state (pass an empty 'status'): then an issue is handed only by a person. agentActions is a list from: describe, subtasks, ask, wiki, report, support. agentFields names the fields it may set: priority, labels, duedate, or the project's own fields by name. doneStatus is the state it moves the issue to when done — never a finished one. capUsd raises what one task may cost, up to 5 dollars. Commenting is always allowed; finishing, deleting, reassigning and anything outside the issue never are. Saving or switching on a duty makes it yours: it runs on your behalf and access, and is switched off if you lose the project. The state and the type are NAMES of this project's own (list_project_rules lists them); an unknown one is refused with the list. Project lead or admin only.

Input parameters:

- `action` (string|null): 'checklist' (the default) or 'agent' for a duty. Fixed once the rule exists.
- `agentActions` (string|null): For a duty: what the agent may do beyond commenting, comma-separated — describe, subtasks, ask, wiki, report, support.
- `agentFields` (string|null): For a duty: the fields it may set, comma-separated — priority, labels, duedate, or a project field's name.
- `agentRole` (string|null): For a duty: the agent role's key, e.g. 'product'.
- `capUsd` (number|null): For a duty: what one task may cost, in dollars, when more than the ordinary ceiling is needed. At most 5.
- `doneStatus` (string|null): For a duty: the state it moves the issue to when done. Never a finished state. Leave out and it does not move the issue.
- `enabled` (boolean): Whether the rule is on from the start. Default true.
- `label` (string|null): Only issues carrying this label (one word). Leave out for any label.
- `name` (string, required): What the rule is called, e.g. 'Document pack'. At most 120 characters.
- `projectKey` (string, required): Project key, e.g. 'ONB'.
- `status` (string, required): The state whose ENTRY fires the rule, by name, e.g. 'Ready for review'. For a duty taken only by hand, an empty string.
- `template` (string, required): The checklist, as Markdown: a '### Heading' per group, '- [ ] item' per line, two spaces of indent to nest an item under another, and [~username] on an item to name who does it. For a duty: the instr…
- `type` (string|null): Only issues of this type, by name, e.g. 'Bug'. Leave out for any type.

### `create_wiki_page` (~359 tokens)

Create a wiki page from Markdown in a space you can write to. Hangs off the space home page unless you name a parent (by 'parentId', or by 'parentTitle' within the same space). Returns the new page id and its URL. Mention someone as [@Their Name](user:username) — on a wiki page that is the form that notifies, not the [~username] used in issues; link another page as [text](page:Title) or [text](page:SPACE/Title). Answers with 'notified' (who this actually reaches) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land.

Input parameters:

- `labels` (string|null): Labels to set, space or comma separated.
- `markdown` (string|null): Page body as Markdown, which also carries $…$ / $$…$$ LaTeX formulas and the wiki's macros on a line of their own — {toc}, {children}, {info} … {info}, {expand:title=…} … {expand}.
- `parentId` (integer|null): Parent page id. Omit to hang the page off the space home page.
- `parentTitle` (string|null): Parent page title, as an alternative to 'parentId'. Must be a page in the same space.
- `spaceKey` (string, required): Space key, e.g. 'PM'. Every project has exactly one space, keyed like the project.
- `title` (string, required): Page title. Must be unique within the space — the title is how pages are addressed here.

### `decide_support_letter` (~135 tokens)

Decide what a held or failed letter to the support address is. 'release' files it as if it had just arrived, past the spam filter and the limits: it joins the request it proves it answers, or opens a new one (and the sender gets the usual acknowledgement). 'dismiss' says it is not a request; its files are dropped and the row is deleted with the other unfiled letters after 30 days. Requires full write access to the support project.

Input parameters:

- `decision` (string, required): 'release' or 'dismiss'.
- `letterId` (integer, required): The letter's id, from list_support_letters.

### `delete_agent_usage` (~94 tokens)

Remove one agent-usage record from an issue, by the id that get_agent_usage returns. For correcting a record's numbers, re-send it with report_agent_usage instead — the same source + externalId updates in place. Requires permission to edit the issue.

Input parameters:

- `id` (integer, required): Record id, as returned by get_agent_usage.
- `key` (string, required): Issue key, e.g. 'UK-1'.

### `delete_attachment` (~99 tokens)

Remove a file from an issue, by 'filename' or by attachment 'id' — both come back from get_issue. Requires permission to edit issues in that project.

Input parameters:

- `filename` (string|null): File name to remove, as listed by get_issue.
- `id` (integer|null): Attachment id, as listed by get_issue. An alternative to 'filename'.
- `key` (string, required): Issue key, e.g. 'UK-1'.

### `delete_project_field` (~78 tokens)

Remove a column from a project. Answers with how many issues lost a value, because that number is what the decision was about — the issues themselves are untouched. Project lead or admin only.

Input parameters:

- `name` (string, required): Field name, e.g. 'Provider'.
- `projectKey` (string, required): Project key, e.g. 'CRM'.

### `delete_project_rule` (~92 tokens)

Remove a project rule, by its id from list_project_rules. Checklists it already added stay in their issues; its journal goes with it. To stop it for a while instead, update_project_rule with enabled=false. Project lead or admin only.

Input parameters:

- `id` (integer, required): The rule's id, from list_project_rules.
- `projectKey` (string, required): Project key, e.g. 'ONB'.

### `delete_wiki_attachment` (~156 tokens)

Remove a file from a wiki page, by its 'filename' as listed by get_wiki_page. Identify the page by 'pageId', or by 'spaceKey' + 'title'. References to it in the page body are left alone — they simply stop resolving, so tidy the body too if it named the file. Requires permission to edit the page.

Input parameters:

- `filename` (string, required): File name to remove, as listed by get_wiki_page.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `delete_wiki_page` (~150 tokens)

Delete a wiki page. Identify it by 'id', or by 'spaceKey' + 'title'. A page with children takes its whole subtree with it, so that is refused unless you pass includeChildren: true — the answer names what would go. The space home page can never be deleted. Requires permission to edit the wiki.

Input parameters:

- `id` (integer|null): Page id (as returned by search_wiki).
- `includeChildren` (boolean): Delete the page's descendants along with it. Off by default.
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `get_agent_usage` (~91 tokens)

What background-agent runs have cost an issue (local or mirrored/upstream): one record per run (tokens by kind, machine time, agent/model/host) plus the totals. A null metric means nobody reported it — which is not the same as zero, so 'reporting' says how many records carried each metric.

Input parameters:

- `key` (string, required): Issue key, e.g. 'UK-1'.

### `get_attachment` (~135 tokens)

Read one file attached to an issue — its bytes, base64-encoded — by the issue key and the 'filename' as get_issue lists it. The bytes travel as text through the conversation and cost roughly a third more than the file itself, so there is a size ceiling; the pm-files MCP server saves a file to a path instead, without spending the conversation on it (local_files_setup says how to connect it). Reading needs only permission to see the issue.

Input parameters:

- `filename` (string, required): File name, as listed by get_issue.
- `key` (string, required): Issue key, e.g. 'UK-1'.

### `get_change` (~85 tokens)

The files one merge request, pull request or commit touches, with their diffs. Large diffs arrive cut.

Input parameters:

- `change` (string, required): The merge request or pull request number ('!12', '#12' or '12'), or a commit's hash.
- `repo` (string|null): Which repository, as 'group/repo'. Omit when only one is open.

### `get_comments` (~240 tokens)

Read the discussion on an issue, oldest first. Without 'startAt' you get the END of the thread — the most recent 'limit' comments — because catching up means reading the latest, not the first ever written; 'earlier' says how many stand before the page you got, and 'startAt' walks back through them. Each 'body' comes back exactly as written, which is also the text update_comment's 'edits' are matched against. A reply carries 'replyTo', the id of the comment it answers (null otherwise) — pass the same id to add_comment's 'replyTo' to answer one. Comments that were deleted are not listed. Needs only permission to see the issue.

Input parameters:

- `key` (string, required): Issue key, e.g. 'UK-1'.
- `limit` (integer): How many comments to return (default 20, max 100).
- `startAt` (integer|null): Where to start, counted from the OLDEST comment as 0. Omit for the most recent page; pass 'startAt' - 'limit' from a previous answer to step back one page.

### `get_issue` (~124 tokens)

Get one issue by key (e.g. 'UK-1') if it is visible to this token, including its description, status, assignee, labels, sub-tasks, the issues it is linked to and the wiki pages linked to it. 'comments' is a COUNT, not the text — read the thread itself with get_comments whenever it is not zero: decisions, the map of what is attached and how to run the thing are often written there rather than in the description.

Input parameters:

- `key` (string, required): Issue key, e.g. 'UK-1'.

### `get_project_rule_runs` (~244 tokens)

A project rule's journal, newest first: each time an issue entered the rule's state and matched its conditions — which issue, who moved it, and the outcome: 'applied' (checklist added), 'alreadyThere' (its marker line was already in the description, nothing added) or 'tooLong' (the description would have been too long, nothing added); for a duty, 'handed' (the issue went to its agent), 'busy' (an agent already had it), 'tooMany' (the duty's fifty a day were used up) or 'refused' with a 'reason' (agent_off, agent_budget, …), and 'ownerLost' when the duty was switched off because its owner lost the project. The place to look when a checklist did not appear or an agent did not take an issue. Any member may read it.

Input parameters:

- `id` (integer, required): The rule's id, from list_project_rules.
- `limit` (integer): How many lines, newest first (default 20, max 100).
- `projectKey` (string, required): Project key, e.g. 'ONB'.

### `get_support_letter` (~95 tokens)

Read one letter to the support address whole — the quoted thread and the signature that the request's comment leaves out, the HTML part as text, and the headers. 'letterId' comes from list_support_letters or from get_support_request's messages. A letter in a request is readable by whoever can read that issue; any other only by the support project's full members.

Input parameters:

- `letterId` (integer, required): The letter's id.

### `get_support_request` (~175 tokens)

Read who is behind a support request: the requester's name and email, whether they asked signed in (and from which workspace), their client version, what the product knew when they asked from inside it ('context': the screen, the client, the last refusal the server gave them with its request id), and which comments of the issue the requester can see ('messages', by comment id and direction; 'autoRule' names the rule when the desk's automatic answer wrote it rather than a person, 'autoWording' says whether the model worded it or the template went). Every other comment on the issue is internal — the requester never sees it. Answers found=false for an issue that is not a support request.

Input parameters:

- `key` (string, required): Issue key of the request, e.g. 'SUPPORT-12'.

### `get_wiki_attachment` (~224 tokens)

Read one file attached to a wiki page — its bytes, base64-encoded — identifying the page by 'pageId' or by 'spaceKey' + 'title' and the file by 'filename' as get_wiki_page lists it. This is how a file is copied from one page to another: read it here, write it with add_wiki_attachment. The bytes travel as text through the conversation and cost roughly a third more than the file itself, so there is a size ceiling; for anything larger — or for putting the file on disk — the pm-files MCP server saves it to a path instead (local_files_setup says how to connect it). Reading needs only permission to see the page.

Input parameters:

- `filename` (string, required): File name, as listed by get_wiki_page.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `get_wiki_page` (~490 tokens)

Get one wiki page by id, or by space key + title, if it is visible to this token. Returns the page text, its labels, its parent and child pages, its comments, and the issues linked to it. Pass withMarkdown: true when you intend to edit it — you then get the page's Markdown source to change and hand back to update_wiki_page, and each comment's own Markdown, version and anchor for update_wiki_comment. A large page is read a part at a time: 'outline' lists its headings and how big each section is, then 'section' returns one of them (or 'offset'/'limit' a run of lines), and update_wiki_page's 'edits' changes just the words you mean without sending the page back.

Input parameters:

- `id` (integer|null): Page id (as returned by search_wiki).
- `limit` (integer|null): How many lines to return from 'offset' (default 200).
- `offset` (integer|null): Return only the Markdown source from this 1-based line on — for a page without headings to navigate by. Pair with 'limit'.
- `outline` (boolean): Return the page's table of contents instead of its body: per heading its level, text, anchor, the 1-based lines its section spans in the Markdown source, and its size in characters. Start here on a l…
- `section` (string|null): Return only this section's Markdown — its heading line down to the next heading of the same or a higher level, subsections included. Name it by its anchor (as 'outline' gives it: the id a {toc} entry…
- `spaceKey` (string|null): Space key, when looking the page up by title.
- `title` (string|null): Exact page title, when looking the page up by title.
- `withMarkdown` (boolean): Return the Markdown source ('markdown') instead of the plain text — what to edit and pass back to update_wiki_page, and, for each comment, to update_wiki_comment. Off by default: reading costs less a…
- `withRelated` (boolean): With 'outline', 'section' or 'offset': also return the page's children, attachments, comments and linked issues, which a partial read otherwise leaves out.

### `link_issues` (~282 tokens)

Link two issues: link_issues('PM-419', 'PM-417', 'is blocked by') records that PM-419 is blocked by PM-417. 'type' is the PHRASE, said as a person says it — 'blocks', 'is blocked by', 'duplicates', 'is duplicated by', 'clones', 'relates to' — so the direction is in the words and the arguments cannot disagree; a bare type name ('Blocks') points outward, as it does in Jira. A phrase this workspace does not have is REFUSED and the ones it does have are named, rather than quietly becoming 'relates to'. The link then shows on both issues (get_issue returns 'links'), and linking the same pair the same way again changes nothing. Requires permission to edit BOTH issues — a link is a change to the issue at each end.

Input parameters:

- `from` (string, required): The issue the phrase is said about, e.g. 'PM-419'.
- `to` (string, required): The issue at the other end, e.g. 'PM-417'.
- `type` (string, required): How 'from' relates to 'to', in words: 'blocks', 'is blocked by', 'duplicates', 'clones', 'relates to'. Whatever this workspace's link types call themselves — a refusal lists them.

### `link_wiki_page` (~186 tokens)

Link a wiki page to an issue. The link then shows on both the issue and the page, and survives the page being renamed or moved. Identify the page by 'pageId', or by 'spaceKey' + 'title'. Linking the same page again updates that link instead of adding a second one. Requires permission to edit the issue.

Input parameters:

- `issueKey` (string, required): Issue key, e.g. 'UK-1'.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `relationship` (string|null): How the page relates to the issue, free text — e.g. 'documentation' (the default), 'specification', 'design'.
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `list_code` (~104 tokens)

List a directory of a repository connected to a project. Only repositories whose owners opened them to agents can be read.

Input parameters:

- `path` (string|null): Directory inside the repository. Omit for its root.
- `reference` (string|null): Branch, tag or commit. Omit for the default branch.
- `repo` (string|null): Which repository, as 'group/repo'. Its last segment alone is enough when unambiguous; omit it when only one is open.

### `list_credentials` (~92 tokens)

List the credentials attached to an issue or a wiki page that this token may read — names and who attached them, never the values. Pass issueKey or pageId. Use reveal_credential to read one value, and only when you are about to use it.

Input parameters:

- `issueKey` (string|null): Issue key, e.g. 'UK-1'.
- `pageId` (integer|null): Wiki page id.

### `list_issue_questions` (~319 tokens)

Questions with answer options, in either direction: what is waiting on YOU to answer (role='to_me'), what YOU asked and are waiting on somebody for (role='asked'), or everything on one issue. This is how you find out whether the question you asked has been answered — poll it, since ask_issue_question does not wait. With no 'key' the default is role='to_me'; with a 'key' it is every question on that issue. Open ones by default; pass state='all' for the answered, withdrawn and expired ones too.

Input parameters:

- `from` (string|null): 'agent' for only what an agent put to somebody (a resolution, PM-602), 'people' for only what a person asked. Omit for both.
- `key` (string|null): Limit to one issue, e.g. 'UK-1'. Omit to look across every project this token can reach.
- `limit` (integer): How many to return (default 20, max 100).
- `role` (string|null): Whose questions: 'to_me' (waiting on you to answer), 'asked' (you asked them, and you are waiting), or 'any'. Defaults to 'to_me' with no key, and to 'any' when a key is given.
- `state` (string|null): 'open' (the default — still waiting on somebody) or 'all', which adds the answered, the withdrawn and the expired (the deadline passed with no default and no answer).

### `list_mcp_connections` (~48 tokens)

List the external MCP connections configured for a project. Auth tokens are never returned (only whether one is set).

Input parameters:

- `projectKey` (string, required): Project key, e.g. 'UK'.

### `list_project_fields` (~71 tokens)

The columns a project keeps about its issues: name, kind, and a list's options with their colours. Also the customfield_… name each one answers to, for searching by JQL. Any member may read them.

Input parameters:

- `projectKey` (string, required): Project key, e.g. 'CRM'.

### `list_project_rules` (~471 tokens)

A project's rules: when an issue enters a state, a checklist from a template is appended to its description. A rule appends its checklist to an issue's description when the issue ENTERS the rule's state — from the board, the Jira API or update_issue alike. The block starts with a hidden marker line '[//]: # (pm-rule:<id>)'; while that line is in the description, entering the state again adds nothing (the journal says 'alreadyThere'). A [~username] in the template is a mention: that person is notified when the checklist lands. A checklist that would make the description too long is not added and the journal says 'tooLong'; the move itself still happens. A rule with action 'agent' is a DUTY instead: it says which issues may be handed to an agent role (agentRole, e.g. 'product') and what the agent may do with them. Its 'template' is then the INSTRUCTION — what doing such an issue means in this project — and nothing is appended anywhere. A duty may have no state (pass an empty 'status'): then an issue is handed only by a person. agentActions is a list from: describe, subtasks, ask, wiki, report, support. agentFields names the fields it may set: priority, labels, duedate, or the project's own fields by name. doneStatus is the state it moves the issue to when done — never a finished one. capUsd raises what one task may cost, up to 5 dollars. Commenting is always allowed; finishing, deleting, reassigning and anything outside the issue never are. Saving or switching on a duty makes it yours: it runs on your behalf and access, and is switched off if you lose the project. Answers with each rule's id (the other rule tools take it), action, state, conditions (type, label — null means any), template and whether it is on, and for a duty its 'agent' part; with the state and type names a rule may use, the agent roles, actions and fields a duty may name where agents are on; and 'canEdit'. Any member may read them.

Input parameters:

- `projectKey` (string, required): Project key, e.g. 'ONB'.

### `list_projects` (~59 tokens)

List the projects visible to this token (a project-scoped token sees only its project). A project with a 'parent' is a subproject of that one: it has its own issues, board and wiki, and shares only who may reach it.

### `list_support_letters` (~159 tokens)

List letters to the support address that did not become part of a request: 'held' (looked like spam, or too many at once from one address or on one request), 'failed' (we could not file them), 'ignored' (an autoresponder, a bounce, a mailing list, our own letter), 'dismissed'. Without 'state', held and failed — the ones waiting for a person. Each has an id, the reason, the sender, the subject and the file names. Only a full member of the support project may read them: they are strangers' words.

Input parameters:

- `state` (string|null): One of 'held', 'failed', 'ignored', 'dismissed'. Omit for held and failed.

### `list_support_queue` (~278 tokens)

The support desk's queue: open requests, the most urgent first — overdue, then due soonest, then those waiting on their requester. Each item says whose turn it is ('clock.turn': 'first' = nobody has answered yet, 'reply' = the requester wrote again, 'requester' = we answered last and the clock is stopped), since when it has waited, the working hours waited, when the answer is due and whether it is 'onTime', 'dueSoon' or 'overdue'; plus the assignee. 'numbers' has the current counts and, over the last 30 days, the median time to a first human answer and to resolution (in working hours — weekdays only) and the share answered within the promise. An automatic acknowledgement or automatic answer is never counted as an answer; 'numbers.auto' says how many requests the automatic answer got, how many were then resolved with no human reply and no further word from the requester ('settled'), and how many requesters wrote again after it ('askedAgain') — per rule too. An item's 'autoRule' is set when the request got an automatic answer. Readable by anybody who can read the support project.

Input parameters:

- `who` (string|null): 'all' (default), 'mine' (assigned to you) or 'unassigned'.

### `list_tracks` (~106 tokens)

The tracks a project is cut into: a track is a LABEL the project declared meaningful — a play, a campaign, a rollout — and work joins one by carrying that label. A track has no key of its own; its issues keep the project's key, which is what makes it a different thing from a subproject (a separate project, see create_project's parentKey). Any member may read them.

Input parameters:

- `projectKey` (string, required): Project key, e.g. 'TH'.

### `list_users` (~174 tokens)

List the people you can assign work to or mention: the members of the projects this token can see. Optionally filter by free text (username, display name or email) or narrow to one project. Mention someone in a description or comment as [~username] — a mention matches the username, never the display name — and pass the same username (or the email) as 'assignee'.

Input parameters:

- `includeInactive` (boolean): Include deactivated accounts, which can be neither assigned nor mentioned. Off by default.
- `limit` (integer): Max users to return (default 50, max 200).
- `projectKey` (string|null): Restrict to members of this project key (must be visible to the token).
- `text` (string|null): Free text to match in username, display name or email.

### `list_wiki_spaces` (~31 tokens)

List the wiki spaces visible to this token. Every project has exactly one space, keyed like the project.

### `local_files_setup` (~126 tokens)

How the person connects pm-files — a small local MCP server that attaches files from their own computer to issues and wiki pages by path, and saves attachments back to files, so no bytes pass through the conversation. Call it when you need a local file (a screenshot, a document, anything past the base64 ceiling) and pm-files is not among your tools: hand the person the command for their client, with this workspace's address already in it. It never contains a token, and you must never ask for one — the command takes it from the person's clipboard. Changes nothing.

### `move_issue` (~268 tokens)

Move an issue into another project, taking its comments, history, attachments, links and sub-tasks with it. The key does NOT change: 'PM-12' stays 'PM-12' wherever it lives, so every existing link and branch name goes on working. Call it with apply=false first (the default): the answer says what the move would cost — values in columns the target project does not have are LOST, the sprint is cleared, and a secret attached to the issue becomes readable by the target project's lead. Refuses when the target has no state by the issue's status name; say what that state becomes with 'statuses'. A sub-task cannot move on its own — move its parent. Needs full write access in BOTH projects.

Input parameters:

- `apply` (boolean): false (the default) answers what the move would do and changes nothing. true does it.
- `key` (string, required): The issue to move, e.g. 'PM-12'.
- `statuses` (string|null): What each source status becomes in the target project, as 'In Review=Review, Blocked=On hold'. Only needed for states the target does not already have under the same name.
- `toProject` (string, required): Project key to move it into, e.g. 'CRM'.

### `read_code` (~107 tokens)

Read one file from a repository connected to a project. A long file arrives cut and says so; generated and binary files are not read.

Input parameters:

- `path` (string, required): The file's path inside the repository, e.g. 'src/app/main.ts'. Not a URL.
- `reference` (string|null): Branch, tag or commit. Omit for the default branch.
- `repo` (string|null): Which repository, as 'group/repo'. Omit when only one is open.

### `remove_track` (~139 tokens)

Undeclare a track. THE LABEL STAYS ON THE WORK: what goes is the declaration, so the issues keep their tag and declaring the track again brings exactly the same ones back. Nothing here can lose work — but the name, icon and place ARE lost, and the track's issues count as 'outside every track' afterwards. For a track that is merely finished, archive it instead: set_track with archived=true. Needs permission to write in the project.

Input parameters:

- `projectKey` (string, required): Project key, e.g. 'TH'.
- `tag` (string, required): The track's tag, e.g. 'cherry-orchard'.

### `reply_to_requester` (~145 tokens)

Write to the person who opened a support request. The text becomes a comment the requester can read on their page and is emailed to them — unlike add_comment, which is always internal and never reaches the requester. Write it to the customer, in their language (get_support_request says which). Requires full write access to the support project. Answers with the comment id and whether the letter went ('emailed'; 'emailError' when it failed — the reply is kept either way).

Input parameters:

- `body` (string, required): What to tell the requester. Markdown is kept as written.
- `key` (string, required): Issue key of the request, e.g. 'SUPPORT-12'.

### `report_agent_usage` (~394 tokens)

Record what one agent run cost an issue (local or mirrored/upstream): tokens by kind and machine time. Re-sending the same 'source' + 'externalId' updates that record instead of adding a second one, so a retry never doubles the spend. Omit a metric you did not measure — omitted is 'not reported', which is different from zero. Requires permission to edit the issue.

Input parameters:

- `agent` (string|null): Which agent did the work, e.g. 'claude-code'.
- `agentSeconds` (integer|null): Wall-clock seconds the run took. Machine time, not a worklog.
- `cacheReadTokens` (integer|null): Cache-read tokens.
- `cacheWriteTokens` (integer|null): Cache-write tokens.
- `costUsd` (number|null): Cost in USD, if you know the rates that applied.
- `endedAt` (string|null): When the run ended (ISO 8601).
- `externalId` (string, required): Your own key for this record (usually the run id). Re-sending it updates the record.
- `host` (string|null): Which host it ran on.
- `inputTokens` (integer|null): Input (prompt) tokens.
- `key` (string, required): Issue key, e.g. 'UK-1'.
- `model` (string|null): Which model it ran on, e.g. 'claude-opus-5'.
- `note` (string|null): Short note, e.g. the outcome.
- `outputTokens` (integer|null): Output (completion) tokens.
- `runId` (string|null): The run this record belongs to, when one run reports several records (different models or hosts).
- `source` (string, required): Who is reporting, e.g. 'bg-agent-api'. Idempotency is scoped to it.
- `startedAt` (string|null): When the run started (ISO 8601).

### `reveal_credential` (~118 tokens)

Read one credential's value, by the id from list_credentials. This is the same act as pressing reveal on the screen and carries the same cost: it is recorded against the token you are using, and the person who owns the secret can see that you read it. Call it when you are about to use the value, not to have it on hand. The answer is marked secret: do not copy it into a comment, a description, a merge request or a log.

Input parameters:

- `id` (integer, required): Credential id, from list_credentials.

### `search_code` (~177 tokens)

Find where a string appears in the code of the repositories connected to a project, with the file, the line number and the lines around it. Only repositories whose owners opened them to agents are searched, and the answer says when what it searched was out of date.

Input parameters:

- `caseSensitive` (boolean): Match case. Off by default.
- `path` (string|null): Narrow to files whose path contains this, such as 'src/' or '.ts'.
- `query` (string, required): What to look for, taken literally unless regexp is set. A space is a space, not two terms.
- `regexp` (boolean): Read query as a regular expression. One with no literal in it may not finish in time.
- `repo` (string|null): Which repository, as 'group/repo'. Omit to search every repository open here.

### `search_issues` (~774 tokens)

Search the issues visible to this token, with a JQL query — the same JQL the Jira REST surface takes, read by the same parser. A query naming a field this tracker does not have is REFUSED and named, never run: it would match everything and read as an answer. Without `jql`, the shorthand arguments filter instead. Most recently created first unless the query says ORDER BY. Archived issues are left out unless the query names `archived`, and `archivedLeftOut` says how many matches that hid — an empty list with it is not "there are none".

Input parameters:

- `jql` (string|null): A JQL query, e.g. `assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC`. FIELDS: project, status, statuscategory, issuetype, type, priority, resolution, assignee, reporter, crea…
- `limit` (integer): Max issues to return (default 25, max 100).
- `projectKey` (string|null): Shorthand for `project = …` (must be visible to the token). Narrows the query further when `jql` is given too.
- `status` (string|null): Shorthand for `status = …` (e.g. 'In Progress', 'Done'). Narrows the query further when `jql` is given too.
- `text` (string|null): Shorthand for `key ~ … OR summary ~ …`. Narrows the query further when `jql` is given too.

### `search_wiki` (~119 tokens)

Search wiki pages visible to this token by free text (matched in the title, the body, and the labels written on an attached SVG diagram). Optionally restrict to one space key. Most recently updated first.

Input parameters:

- `limit` (integer): Max pages to return (default 25, max 100).
- `spaceKey` (string|null): Restrict to this space key (e.g. 'PM').
- `text` (string|null): Free text to match in the page title, its body, or the text of an attached SVG diagram.

### `set_issue_fields` (~185 tokens)

Fill in a project's own columns on one issue, by their NAMES: 'Segment=AI; Country=Poland; Started=2026-08-01', or a JSON object. A country column takes the country's name or its two-letter code. An empty value clears the field, which is not the same as an empty one. A value that does not fit the field's kind — an option nobody offered, a date that is not one, a country that is neither — refuses the WHOLE call rather than writing part of it.

Input parameters:

- `key` (string, required): Issue key, e.g. 'CRM-1'.
- `values` (string, required): Field names to values, as 'Segment=AI; Country=Poland' or a JSON object. List values are given by option name, people by username, dates as 2026-08-21.

### `set_project_field` (~377 tokens)

Add a column to a project, or edit the one that already has this name. Keyed by name rather than by id: 'make sure there is a Segment field with these options' is the request, and a name is what you just read. A field's KIND cannot be changed once it exists. Project lead or admin only.

Input parameters:

- `icon` (string|null): The glyph that mark wears, from the palette list_project_fields returns. Not for a country field: that one marks a card with the flag of the country the issue holds, so passing an icon is refused rat…
- `kind` (string|null): What it holds: 'text', 'number', 'date', 'user', 'select' (a list) or 'country' (a country, written as its name or its two-letter code and shown with a flag). Required when creating; ignored when the…
- `name` (string, required): Field name, e.g. 'Segment'. An existing field with this name is edited rather than duplicated.
- `newName` (string|null): Rename the field to this. Leave out to keep the name.
- `options` (string|null): For a list: its options, as 'AI:green, Skins:orange, GameCards' or a JSON array of {name, color}. A colour is optional and must be one of: orange, amber, red, pink, purple, blue, teal, green. Options…
- `projectKey` (string, required): Project key, e.g. 'CRM'.
- `showIcon` (boolean|null): Mark a board card when this field has a value: true to switch the mark on, false off. Leave out to keep it as it is.

### `set_project_parent` (~193 tokens)

Make an existing project a subproject of another, or take it back out with an empty parentKey. This is about ACCESS, not about names or issues: everyone who may reach the parent may reach the subproject too, in the role they hold there. Nothing is renamed, no issue moves, and the keys stay ordinary keys. Attaching needs an admin or somebody who leads BOTH projects, since neither can be volunteered by whoever leads the other; detaching needs a lead of either, since it only takes access away. One level: a subproject cannot have subprojects.

Input parameters:

- `parentKey` (string|null): Key of the project it becomes a subproject of, e.g. 'KYC'. Leave it out (or empty) to detach it and leave it a project of its own.
- `projectKey` (string, required): Key of the project to move, e.g. 'KYCB2B'.

### `set_track` (~458 tokens)

Declare a track on a project, or edit the one that already has this tag. The tag is the LABEL issues carry to join it ('cherry-orchard'), so it cannot contain a space — labels are stored space-separated. The name is what a reader sees ('The Cherry Orchard'); leave it out and the tag speaks for itself. Declaring a track writes nothing to any issue: the ones already carrying the label are in it at once. Needs permission to write in the project.

Input parameters:

- `archived` (boolean|null): true takes the track off the row above the board WITHOUT undeclaring it — a play that has been staged: its name, icon, place and tag are kept, its issues stay in it (they do not become 'outside every…
- `icon` (string|null): One or two emoji shown BESIDE the name, never instead of it. Leave it out to keep what is there; send an empty string to take it off. Anything that is not an emoji is refused rather than ignored.
- `name` (string|null): What a reader sees, e.g. 'The Cherry Orchard'. Leave it out to keep what is there; send an empty string to drop it and let the tag speak.
- `newTag` (string|null): Rename the tag to this. The label on existing issues is NOT rewritten, so work in the old tag leaves the track — rename only when the issues are being relabelled too.
- `position` (integer|null): Where it sits in the row above the board, counting from 1. Leave it out and a new track goes last, an existing one stays where it is.
- `projectKey` (string, required): Project key, e.g. 'TH'.
- `tag` (string, required): The label that puts an issue in this track, e.g. 'cherry-orchard'. No spaces. An existing track with this tag is edited rather than duplicated; matched exactly, so 'Hamlet' and 'hamlet' are two tags,…

### `unlink_issues` (~209 tokens)

Remove a link between two issues: name both ends (and, when more than one link joins them, the phrase — 'blocks', 'relates to'), or give the 'linkId' get_issue returned. Ambiguity is refused rather than guessed: two issues joined twice and no phrase given comes back with the links listed. Requires permission to edit BOTH issues. The issues themselves are untouched.

Input parameters:

- `from` (string|null): One end, e.g. 'PM-419'. Not needed when 'linkId' is given.
- `linkId` (integer|null): The link's id, as get_issue returns it in 'links'.
- `to` (string|null): The other end, e.g. 'PM-417'. Not needed when 'linkId' is given.
- `type` (string|null): Which link, when the two issues are joined by more than one: the phrase as get_issue says it ('blocks', 'is blocked by', 'relates to').

### `unlink_wiki_page` (~125 tokens)

Remove the link between an issue and a wiki page. Identify the page by 'pageId', or by 'spaceKey' + 'title'. Requires permission to edit the issue. The page itself is not touched.

Input parameters:

- `issueKey` (string, required): Issue key, e.g. 'UK-1'.
- `pageId` (integer|null): Page id (as returned by search_wiki).
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Exact page title, when identifying the page by title.

### `update_comment` (~381 tokens)

Edit a comment on an issue: pass 'body' to replace the whole of it, or 'edits' to change part of it — exact find-and-replace against the comment as get_comments hands it back, each 'oldText' occurring exactly once. One body axis per call. With 'edits' the answer says where each one landed rather than giving the comment back, so correcting one number in a long comment costs neither a resend nor a re-read. Nothing here takes a version and none is needed: an 'oldText' is matched against the comment as it stands now, so an edit whose text somebody has since rewritten is refused rather than applied over their words. The author may edit their own comment; anyone with full write access in the project may edit any. Answers with 'notified' — and note that this is only the people the *edit* newly reaches: names already present in the old text are not notified again, and the comment is not re-announced to watchers.

Input parameters:

- `body` (string|null): The new body, replacing the old one entirely. Markdown is kept as written, and carries $…$ / $$…$$ LaTeX formulas and [~username] mentions.
- `commentId` (integer, required): The comment's id, as returned by get_comments or add_comment.
- `edits` (array|null): Exact replacements in the comment, changing part of it in place. Matched against the body as get_comments returns it — stored as written, so what you read is what an 'oldText' has to equal, whitespac…
- `key` (string, required): Issue key the comment is on, e.g. 'UK-1'.

### `update_issue` (~760 tokens)

Update an issue: any of summary, description, type, assignee, priority, labels, dueDate, parentKey, or status (which performs the transition, and fires the project's rules — a rule may append a checklist to the description, in which case 'description' is listed in 'changed'). Only the fields you pass are changed. A project's workflow may put conditions on a move — that only certain people may make it, or that it must set certain fields — and a refusal names the condition and, where it is about who you are, lists the states you CAN reach. Fields a move requires must be sent in this same call, with 'resolution' and 'fields': a value set by an earlier call does not satisfy the condition. Answers with 'notified' (who this actually reaches) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land.

Input parameters:

- `assignee` (string|null): Assignee username or email. Pass an empty string to unassign. 'agent:<role>' (e.g. 'agent:product') hands the issue to that agent instead: you become the assignee and the agent works on your behalf —…
- `description` (string|null): New description. Markdown, with $…$ / $$…$$ LaTeX formulas and [~username] mentions.
- `detachParent` (boolean): Take this issue out from under its parent and leave it top-level. The reliable way to detach: unlike an empty parentKey it cannot arrive as the two characters "" or be dropped as «nothing was sent».…
- `dueDate` (string|null): The day this is due, as YYYY-MM-DD. A calendar day, not a timestamp. Pass an empty string to clear it.
- `fields` (string|null): The project's own columns to fill in with this update, by their NAMES: 'Segment=AI; Country=Poland', or a JSON object. Same shape as set_issue_fields — this exists here because a move a workflow cond…
- `key` (string, required): Issue key, e.g. 'UK-1'.
- `labels` (string|null): Labels to set, space or comma separated. Replaces the existing set.
- `parentKey` (string|null): Key of the issue this one hangs off, e.g. 'PM-276' — moves it under that story or epic. To DETACH it instead, send detachParent: true; an empty string still works but is easy to lose on the way. Noth…
- `priority` (string|null): Priority name, e.g. 'High'.
- `resolution` (string|null): Resolution name to set, e.g. 'Fixed', 'Won't Do'. Some workflows require one on the move into a finished state; sending it here is how that condition is met.
- `status` (string|null): Status name to move the issue to, e.g. 'In Progress', 'Done'.
- `summary` (string|null): New summary (title).
- `type` (string|null): New issue type name, e.g. 'Task', 'Bug', 'Story', 'Epic'.

### `update_project_rule` (~731 tokens)

Change a project rule, by its id from list_project_rules. Only what you pass changes: pass just 'enabled' to switch a rule on or off, or any of the others to edit it. An empty string for 'type' or 'label' removes that condition; for a duty, an empty 'status', 'agentActions', 'agentFields' or 'doneStatus' clears that one. A rule's action cannot change. A rule appends its checklist to an issue's description when the issue ENTERS the rule's state — from the board, the Jira API or update_issue alike. The block starts with a hidden marker line '[//]: # (pm-rule:<id>)'; while that line is in the description, entering the state again adds nothing (the journal says 'alreadyThere'). A [~username] in the template is a mention: that person is notified when the checklist lands. A checklist that would make the description too long is not added and the journal says 'tooLong'; the move itself still happens. A rule with action 'agent' is a DUTY instead: it says which issues may be handed to an agent role (agentRole, e.g. 'product') and what the agent may do with them. Its 'template' is then the INSTRUCTION — what doing such an issue means in this project — and nothing is appended anywhere. A duty may have no state (pass an empty 'status'): then an issue is handed only by a person. agentActions is a list from: describe, subtasks, ask, wiki, report, support. agentFields names the fields it may set: priority, labels, duedate, or the project's own fields by name. doneStatus is the state it moves the issue to when done — never a finished one. capUsd raises what one task may cost, up to 5 dollars. Commenting is always allowed; finishing, deleting, reassigning and anything outside the issue never are. Saving or switching on a duty makes it yours: it runs on your behalf and access, and is switched off if you lose the project. Project lead or admin only.

Input parameters:

- `agentActions` (string|null): For a duty: the whole new list of actions, comma-separated.
- `agentFields` (string|null): For a duty: the whole new list of fields, comma-separated.
- `agentRole` (string|null): For a duty: a new agent role key.
- `capUsd` (number|null): For a duty: what one task may cost, in dollars, at most 5; 0 goes back to the ordinary ceiling.
- `doneStatus` (string|null): For a duty: a new review state; an empty string and it no longer moves the issue.
- `enabled` (boolean|null): true to switch the rule on, false to switch it off.
- `id` (integer, required): The rule's id, from list_project_rules.
- `label` (string|null): Only issues with this label. An empty string means any label.
- `name` (string|null): A new name.
- `projectKey` (string, required): Project key, e.g. 'ONB'.
- `status` (string|null): A new state to fire on, by name.
- `template` (string|null): A new checklist template (Markdown). Replaces the old one for issues that enter the state from now on; checklists already added stay as they are.
- `type` (string|null): Only issues of this type, by name. An empty string means any type.

### `update_wiki_comment` (~494 tokens)

Edit a comment on a wiki page — your own, or anyone's with full write access in that project. Read it first with get_wiki_page(withMarkdown: true), which gives each comment its 'markdown' and 'version'. Pass 'markdown' to replace the whole comment, or 'edits' to change part of it (exact find-and-replace on that Markdown, each 'oldText' occurring exactly once) — the answer then says where each landed, not the comment. An inline comment's anchor moves with 'inlineSelection': the page text it should point at, copied exactly; an empty string turns it into an ordinary footer comment, which is the only way it ever stops being anchored. Mention someone as [@Their Name](user:username), the wiki's own form. Answers with 'notified' — only the people the *edit* newly reaches, since names already in the old text are not notified again and the page is not re-announced to its watchers.

Input parameters:

- `commentId` (integer, required): The comment's id, as get_wiki_page lists it or add_wiki_comment returned.
- `edits` (array|null): Exact replacements in the comment's Markdown — the text get_wiki_page returns as a comment's 'markdown'. Every 'oldText' is matched against it as you read it (not against the result of the other edit…
- `expectedVersion` (integer|null): The version you read (from get_wiki_page). If the comment has moved on since, the edit is refused instead of overwriting somebody else's.
- `inlineMatch` (integer|null): Which occurrence of 'inlineSelection' on the page to anchor to, counted from 1. Only needed when the text occurs more than once.
- `inlineSelection` (string|null): Move the inline anchor: the page text this comment should point at, copied exactly from the page. An empty string detaches it, making it a footer comment. Refused when the page does not carry that te…
- `markdown` (string|null): The whole comment, replacing what is there. Markdown, which also carries $…$ / $$…$$ LaTeX formulas.

### `update_wiki_page` (~783 tokens)

Edit a wiki page: retitle it, change its body, move it under another parent (or to the top of its space), or set its labels. Identify it by 'id', or by 'spaceKey' + 'title'. To change part of the body, pass 'edits' (exact find-and-replace on the Markdown source, each 'oldText' occurring exactly once) or 'replaceSection' — the answer says where each landed, not the page, so a one-line fix to a long page costs one line. 'markdown' replaces the whole body — read it first with get_wiki_page(withMarkdown: true) and edit that, or the page's links, images and macros are dropped; 'appendMarkdown' adds to the end and leaves what is there untouched. Mention someone as [@Their Name](user:username), the wiki's own form. Requires permission to edit the page. Answers with 'notified' (who this actually reaches) and 'unresolvedMentions' (what looked like a mention and reached nobody, each with a reason) — a wrong handle is stored as written and never refused, so that list is the only sign it did not land.

Input parameters:

- `appendMarkdown` (string|null): Markdown to add to the end of the page, leaving the rest as it is. Same syntax as 'markdown'.
- `edits` (array|null): Exact replacements in the page's Markdown source — the text get_wiki_page returns with 'section', 'offset' or withMarkdown: true. Every 'oldText' is matched against the page as you read it (not again…
- `expectedVersion` (integer|null): The version you read (from get_wiki_page). If the page has moved on since, the edit is refused instead of overwriting someone else's.
- `id` (integer|null): Page id (as returned by search_wiki).
- `labels` (string|null): Labels to set, space or comma separated. Replaces the existing set.
- `markdown` (string|null): The complete new body as Markdown — replaces what is there. Carries $…$ / $$…$$ LaTeX formulas and the wiki's macros on a line of their own ({toc}, {children}, {info} … {info}, {expand:title=…} … {ex…
- `message` (string|null): Short note for the page history, e.g. why you changed it.
- `minorEdit` (boolean): Mark this as a minor edit in the history.
- `newTitle` (string|null): New title. The page's existing links follow the rename.
- `parentId` (integer|null): Move the page under this parent page id.
- `parentTitle` (string|null): Move the page under the page with this title, in the same space.
- `replaceSection` (object|null): Replace one whole section: 'section' names it as get_wiki_page's 'section' does (anchor or heading text), 'markdown' is what replaces it — heading line included, as reading the section returned it. C…
- `spaceKey` (string|null): Space key, when identifying the page by title.
- `title` (string|null): Current page title, when identifying the page by title.
- `toTopLevel` (boolean): Move the page to the top of its space, out from under any parent, so it sits beside the space's other root pages. Use this instead of parentId/parentTitle — a tool argument has no way to say 'no pare…

### `upsert_mcp_connection` (~162 tokens)

Admin only: create or update a project's external MCP connection. Pass 'id' to update an existing one (omit to create). 'token' is stored server-side and never returned; omit it to keep the current token when updating.

Input parameters:

- `enabled` (boolean|null): Whether the connection is enabled.
- `id` (integer|null): Existing connection id to update; omit to create a new one.
- `name` (string|null): Connection display name (required when creating).
- `projectKey` (string, required): Project key, e.g. 'UK'.
- `token` (string|null): Bearer auth token. Omit to keep the current token when updating.
- `url` (string|null): MCP server URL — use https (required when creating).

### `withdraw_issue_question` (~94 tokens)

Take back a question you asked, when it no longer needs answering — you worked it out, or the task moved on. The comment stays in the thread (the conversation happened) and the question stops counting on the other person's list. The person who asked, or anybody with full write access in the project.

Input parameters:

- `questionId` (integer, required): The question's id, as returned by list_issue_questions or ask_issue_question.

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/tech-kaiku-kaiku/mcp#diagnostics

## Score history

- 2026-09-23: 74
- 2026-09-22: 74

## Common questions

### What is the Kaiku MCP server?

Kaiku is an MCP server listed in the public MCP registry as tech.kaiku/kaiku. Issue tracker and wiki for teams and their agents: find, file and update issues, write pages. This page covers its hosted endpoint (https://mcp.kaiku.tech/mcp).

### Is the Kaiku MCP server safe to use?

Kaiku scores 74 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Kaiku MCP server expose?

Kaiku exposes 74 tools: add_attachment, add_comment, add_wiki_attachment, add_wiki_comment, admin_bank_transfer, and 69 more. Their descriptions and schemas cost roughly 18,469 tokens of context every time the server is loaded.

### Does the Kaiku MCP server require authentication?

Yes. Kaiku asked us for credentials when we connected, so you will need to authorise it in your MCP client before it can do anything.

### Is the Kaiku MCP server still maintained?

Kaiku is still listed as active in the MCP registry. We last reached this channel on 23 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- Remote endpoint: https://mcp.kaiku.tech/mcp
- Authorisation metadata: https://mcp.kaiku.tech/.well-known/oauth-protected-resource/mcp
- Website: https://kaiku.tech/en/docs/mcp?utm_source=mcp-registry
- Changelog RSS feed: https://verifymcp.io/servers/tech-kaiku-kaiku/mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/tech-kaiku-kaiku/mcp.json
- HTML version of this page: https://verifymcp.io/servers/tech-kaiku-kaiku/mcp
