关于 taiga-mcp-server
taiga-mcp-server 让 AI Agent 读取和管理 Taiga 项目工作,包括用户故事、任务、问题、分配和迭代。它按项目内的名称解析状态、成员和迭代,并提供基于时间线的活动视图。
主要功能
- 01读取已分配工作和项目数据
- 02创建和更新用户故事、任务、问题及史诗
- 03管理分配、估算、评论和迭代
- 04按名称解析目录值
- 05汇总指定时间段的项目活动
适用场景
- 01查看分配给团队成员的事项
- 02变更工作事项的项目状态
- 03创建并分配 Taiga 工作事项
- 04查看项目上周的变更
MCP 相关
此仓库包含 taiga-api-mcp-server,可将 Taiga 项目数据和管理操作作为工具提供给 MCP 客户端。它使用 Taiga API 客户端,并遵循账户权限。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y taiga-api-mcp-serverTAIGA_URLTaiga instance root, without /api/v1. Taiga Cloud: https://api.taiga.ioTAIGA_USERNAMETaiga username or email, used together with TAIGA_PASSWORDTAIGA_PASSWORD密钥Password for TAIGA_USERNAMETAIGA_APP_TOKEN密钥Long-lived Application token, instead of a username and passwordTAIGA_PROJECTDefault project (id, slug or name) for tool calls that omit oneTAIGA_MCP_READ_ONLYSet to 1 to register the read tools onlyTAIGA_MCP_ALLOW_DELETESet to 1 to also register taiga_delete_item本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
taiga-api-client
🇬🇧 English version — you are here · 🇷🇺 Русская версия
Taiga REST API 的 TypeScript 客户端。零运行时依赖,同时支持 ESM + CJS,可在 Node ≥18、Bun ≥1.0 及任何提供全局 fetch 的环境中运行。
- 🔐 Bearer 身份验证,支持自动刷新令牌并对并发刷新请求去重
- 📦 类型完整:为每种资源、负载和参数提供类型
- 🚫 零运行时依赖
- 🔄 用于分页的异步迭代器
- ⚠️ 类型化错误(
TaigaApiError、TaigaAuthError、TaigaRateLimitError、TaigaNetworkError) - 🚀 同时构建 ESM + CJS,并提供
.d.ts/.d.cts类型映射
安装
npm install taiga-api-client
# or
bun add taiga-api-client
# or
pnpm add taiga-api-client
快速入门
import { TaigaClient } from "taiga-api-client";
// baseUrl is the only required option.
const client = new TaigaClient({ baseUrl: "https://api.taiga.io" });
// Log in (the `username` field accepts both username and email).
await client.auth.login({ username: "[email protected]", password: "secret" });
// Fetch projects you are a member of.
const me = await client.users.me();
const projects = await client.projects.list({ member: me.id });
// Create a user story.
const story = await client.userStories.create({
project: projects[0].id,
subject: "Add OAuth login",
description: "Support GitHub OAuth in addition to password login",
});
// Iterate over every issue in the project (auto-pagination).
for await (const issue of client.issues.paginate({ project: projects[0].id })) {
console.log(`#${issue.ref} ${issue.subject}`);
}
🤖 MCP 服务:面向 AI Agent 的 Taiga
此仓库还包含 taiga-api-mcp-server——一个基于此客户端构建的模型上下文协议服务。 将 MCP 客户端(Claude Code、Claude Desktop、Cursor 等)连接到你的 Taiga 账户后,它便可以读取分配给你的事项、变更事项状态、创建用户故事、 任务、问题和史诗、分配人员、估算、评论和规划迭代——权限仅限于你自己的权限。
状态、成员和迭代按名称指定;服务会在项目自己的目录中查找对应项,因此 Agent 无须猜测数字 ID。
它还可以通过一次调用回答**“我们上周做了什么”**:taiga_activity 会遍历项目时间线,并针对时间范围内涉及的每张卡片返回实际发生的情况——状态变更、关闭(包括几天后才录入的关闭)、评论、迭代变更和附件。
Taiga 的列表端点没有日期筛选,因此这是获取某个时间段工作情况的唯一方式。
故事是否完成取决于其任务(tasks_done 和 tasks_total),是否关闭则取决于项目自己的状态——日期和关闭标记都需要手动填写,而且通常根本不会填写。
这两项都会分别按当前时间和时间范围结束时的情况回答,因为一张卡片三天后才移至 Done,就不属于上周的工作。
claude mcp add taiga --env TAIGA_URL=https://api.taiga.io --env [email protected] --env TAIGA_PASSWORD=... -- npx -y taiga-api-mcp-server
📖 设置说明、工具参考和示例:mcp/README.md
💡 查找目录 ID?使用 filters_data
大多数写入操作都需要项目特定目录值的数字 ID(状态、类型、严重程度、优先级、受派人等)。不要分别获取每个目录——每种资源都提供 filters_data 方法,可在一次请求中返回所有目录值,并限定在项目范围内。
const data = await client.issues.filtersData({ project: 42 });
const inProgress = (data.statuses as Array<{ id: number; name: string }>)
.find((s) => s.name === "In progress");
await client.issues.create({ project: 42, subject: "...", status: inProgress!.id });
📖 阅读完整指南:docs/filters-data.md——了解缓存、各资源可用的端点,以及哪些情况下它还不够。
创建客户端
最低配置:
const client = new TaigaClient({ baseUrl: "https://api.taiga.io" });
baseUrl 是唯一必填的选项。其他选项均为可选。
完整示例:
const client = new TaigaClient({
baseUrl: "https://api.taiga.io", // ← required
// Authentication — pick one (or none, if you will call client.auth.login later):
token: "...", // a previously obtained bearer token (session restore)
refreshToken: "...", // its paired refresh token
applicationToken: "...", // a static Application token, used instead of bearer
// Behavior:
autoRefresh: true, // auto-refresh the bearer token on 401 (default true)
timeoutMs: 30000, // request timeout in ms (default 30000)
userAgent: "my-app/1.0",
acceptLanguage: "en",
apiPrefix: "/api/v1", // change only for self-hosted with a non-default prefix
fetch: customFetch, // dependency-injected fetch (proxies, mocks)
// Token persistence hook:
onTokenChange: async (tokens) => {
if (tokens) await db.saveTokens(tokens);
else await db.clearTokens();
},
});
TaigaClientOptions
| Option | 是否必填 | Type | Default | Description |
|---|---|---|---|---|
baseUrl | ✅ 是 | string | — | Taiga 实例的根 URL(不包含 /api/v1)。对于 Taiga Cloud:https://api.taiga.io。 |
token | 否 | string | null | null | Bearer 令牌。传入该令牌可恢复现有会话。 |
refreshToken | 否 | string | null | null | 配对的刷新令牌。 |
applicationToken | 否 | string | null | null | 静态应用令牌(与 Bearer 令牌互斥)。 |
autoRefresh | 否 | boolean | true | 收到 401 时自动刷新 Bearer 令牌。 |
timeoutMs | 否 | number | 30000 | 每个请求的超时时间,单位为毫秒。 |
userAgent | 否 | string | — | User-Agent 请求头。 |
acceptLanguage | 否 | string | — | Accept-Language 请求头(响应字符串的国际化设置)。 |
apiPrefix | 否 | string | /api/v1 | API 路径前缀。仅在非标准的可自托管配置中更改。 |
fetch | 否 | typeof fetch | global | 自定义 fetch 实现(代理、模拟、MSW)。 |
onTokenChange | 否 | (tokens | null) => void | Promise | — | 在 login 后以及自动刷新成功后触发的回调。可用它持久化令牌。 |
身份验证
支持三种模式:
| Mode | 使用场景 | Header |
|---|---|---|
| Bearer + refresh | 标准交互式登录 | Authorization: Bearer X |
| 会话恢复 | 进程重启时——将已保存的令牌传给构造函数 | Authorization: Bearer X |
| 应用令牌 | 集成 / 机器人,长期有效的静态令牌 | Authorization: Application X |
自动刷新令牌
启用 autoRefresh: true(默认值)后,客户端会透明地拦截 401 响应,调用 POST /auth/refresh,并仅重试一次原始请求。并发的 401 会被合并去重——即使 10 个请求同时失败,也只会调用一次 /auth/refresh。如果刷新本身失败,令牌会被清除,并抛出 TaigaAuthError。
在进程重启后保留令牌
const client = new TaigaClient({
baseUrl: "https://api.taiga.io",
token: await db.loadToken(),
refreshToken: await db.loadRefreshToken(),
onTokenChange: async (tokens) => {
if (tokens) await db.saveTokens(tokens);
else await db.clearTokens();
},
});
以下情况会调用 onTokenChange:
client.auth.login(...)后——传入新的令牌对;- 自动刷新成功后——传入新的令牌对;
client.auth.logout()后——传入null。
重试请求前会等待返回的 Promise,因此可以确保持久化操作在下一个请求发出前完成。
资源和方法
每个资源都继承自 BaseResource,该类提供标准 CRUD 操作。各资源会在此基础上添加自己的方法。
BaseResource<T> — 通用 CRUD
| Method | HTTP | Returns | Description |
|---|---|---|---|
list(params?) | GET /resource | T[] | 第一页(或在 disablePagination 时返回完整响应)。 |
paginate(params?) | GET /resource?page=N | AsyncIterable<T> | 延迟异步迭代器,遍历 x-pagination-next 链接。 |
listPage(params?, page?) | GET /resource?page=N | { data, pagination } | 指定页面及分页元数据。 |
get(id) | GET /resource/:id | T | 按 ID 获取单个资源。 |
create(payload) | POST /resource | T | 创建。 |
update(id, payload) | PUT /resource/:id | T | 完全替换。必须提供所有必填字段。 |
patch(id, payload, version?) | PATCH /resource/:id | T | 部分更新。version 用于启用乐观并发控制。 |
delete(id) | DELETE /resource/:id | void | 删除。 |
client.auth — 身份验证
| Method | Returns | Description |
|---|---|---|
login({ username, password }) | Promise<AuthResponse> | POST /auth。username 可接受用户名或电子邮件地址。 |
refresh() | Promise<string> | 手动刷新。通常会在收到 401 时自动调用。 |
logout() | Promise<void> | 在本地清除令牌并调用 onTokenChange(null)。 |
setTokens(tokens | null) | Promise<void> | 手动设置令牌(例如,从数据库恢复后)。 |
getTokens() | AuthTokens | null | 当前内存中的令牌。 |
getCurrentUser() | AuthUser | null | 上次成功调用 login() 后的用户资料。 |
onTokenChange(handler) | void | 在运行时替换持久化回调。 |
const auth = await client.auth.login({ username: "u", password: "p" });
console.log(auth.auth_token, auth.refresh, auth.full_name);
client.users — 用户
基础 CRUD 以及:
| Method | HTTP | Description |
|---|---|---|
me() | GET /users/me | 当前已通过身份验证的用户。 |
updateMe(payload) | PUT /users/:id (own id) | update(me.id, …) 的便捷快捷方式。 |
changePassword(currentPassword, newPassword) | POST /users/change_password | 更改密码。 |
stats(id) | GET /users/:id/stats | 用户统计信息。 |
⚠️
users.create()会有意抛出异常——注册使用专用端点(不在 v0.1 范围内)。
const me = await client.users.me();
await client.users.updateMe({ bio: "TypeScript fan", lang: "en" });
await client.users.changePassword("oldPwd!", "newPwd!");
client.projects — 项目
基础 CRUD,另包括:
| 方法 | HTTP | 描述 |
|---|---|---|
getBySlug(slug) | GET /projects/by_slug?slug=... | 根据 slug 而非 id 获取项目。 |
stats(id) | GET /projects/:id/stats | 项目统计信息(点数、里程碑)。 |
modulesConfig(id) | GET /projects/:id/modules | 集成配置(GitHub、GitLab、Bitbucket、Gogs)。 |
setModulesConfig(id, config) | PATCH /projects/:id/modules | 更新集成配置。 |
const proj = await client.projects.create({ name: "Demo", description: "" });
const sameProj = await client.projects.getBySlug(proj.slug);
const updated = await client.projects.patch(proj.id, { description: "Updated" }, /* version */ 1);
const stats = await client.projects.stats(proj.id);
await client.projects.delete(proj.id);
client.memberships — 项目成员关系
基础 CRUD,另包括:
| 方法 | HTTP | 描述 |
|---|---|---|
bulkCreate(payload) | POST /memberships/bulk_create | 一次调用邀请多位用户。 |
resendInvitation(id) | POST /memberships/:id/resend_invitation | 重新发送邀请邮件。 |
await client.memberships.bulkCreate({
project_id: proj.id,
bulk_memberships: [
{ role_id: 1, username: "[email protected]" },
{ role_id: 2, username: "[email protected]" },
],
invitation_extra_text: "Welcome to the team",
});
client.userStories — 用户故事
基础 CRUD,另包括:
| 方法 | HTTP | 描述 |
|---|---|---|
bulkCreate(payload) | POST /userstories/bulk_create | 从以换行符分隔的字符串创建多个故事。 |
bulkUpdateBacklogOrder(payload) | POST /userstories/bulk_update_backlog_order | 重新排列待办列表中的故事。 |
bulkUpdateKanbanOrder(payload) | POST /userstories/bulk_update_kanban_order | 重新排列看板上的故事。 |
bulkUpdateSprintOrder(payload) | POST /userstories/bulk_update_sprint_order | 重新排列冲刺中的故事。 |
filtersData({ project }) | GET /userstories/filters_data?project=... | UI 可用筛选条件:状态、标签、受派人。 |
const story = await client.userStories.create({
project: proj.id,
subject: "User can log in via GitHub",
description: "OAuth flow",
});
// Bulk create from a multi-line string (the way the Taiga UI does it):
await client.userStories.bulkCreate({
project_id: proj.id,
bulk_stories: "Story 1\nStory 2\nStory 3",
});
// Iterate over every story in the project:
for await (const s of client.userStories.paginate({ project: proj.id })) {
console.log(s.subject);
}
client.tasks — 任务
基础 CRUD,另包括:
| 方法 | HTTP | 描述 |
|---|---|---|
bulkCreate(payload) | POST /tasks/bulk_create | 一次创建多个任务。 |
filtersData({ project }) | GET /tasks/filters_data?project=... | 可用筛选条件。 |
const task = await client.tasks.create({
project: proj.id,
subject: "Implement OAuth callback",
user_story: story.id, // link to a user story
});
client.issues — 问题
基础 CRUD,以及:
| 方法 | HTTP | 描述 |
|---|---|---|
filtersData({ project }) | GET /issues/filters_data?project=... | 可用筛选条件(类型、严重程度、优先级)。 |
voters(id) | GET /issues/:id/voters | 对问题投过赞的用户。 |
upvote(id) | POST /issues/:id/upvote | 为问题点赞。 |
downvote(id) | POST /issues/:id/downvote | 取消赞。 |
watchers(id) | GET /issues/:id/watchers | 关注该问题的用户。 |
watch(id) | POST /issues/:id/watch | 订阅通知。 |
unwatch(id) | POST /issues/:id/unwatch | 取消订阅。 |
const issue = await client.issues.create({
project: proj.id,
subject: "Login button doesn't work in Safari",
description: "Reproduces on macOS Safari 17.x",
});
await client.issues.upvote(issue.id);
await client.issues.watch(issue.id);
client.epics — 史诗
基础 CRUD,以及:
| 方法 | HTTP | 描述 |
|---|---|---|
bulkCreate(payload) | POST /epics/bulk_create | 根据以换行符分隔的字符串创建多个史诗。 |
filtersData({ project }) | GET /epics/filters_data?project=... | UI 的可用筛选条件。 |
relatedUserStories(id) | GET /epics/:id/related_userstories | 与该史诗关联的用户故事。 |
addRelatedUserStory(id, { user_story }) | POST /epics/:id/related_userstories | 将现有用户故事关联到该史诗。 |
removeRelatedUserStory(id, usId) | DELETE /epics/:id/related_userstories/:usId | 取消用户故事与该史诗的关联。 |
voters(id) | GET /epics/:id/voters | 对该史诗投过赞的用户。 |
upvote(id) | POST /epics/:id/upvote | 为史诗点赞。 |
downvote(id) | POST /epics/:id/downvote | 取消赞。 |
watchers(id) | GET /epics/:id/watchers | 关注该史诗的用户。 |
watch(id) | POST /epics/:id/watch | 订阅通知。 |
unwatch(id) | POST /epics/:id/unwatch | 取消订阅。 |
const epic = await client.epics.create({
project: proj.id,
subject: "Q1 roadmap",
color: "#ff8800",
});
await client.epics.addRelatedUserStory(epic.id, { user_story: story.id });
for (const link of await client.epics.relatedUserStories(epic.id)) {
console.log(link.user_story);
}
ℹ️ 项目必须启用史诗(
is_epics_activated: true),这些端点才能正常工作。
client.milestones — 冲刺
基础 CRUD,以及:
| 方法 | HTTP | 描述 |
|---|---|---|
stats(id) | GET /milestones/:id/stats | 燃尽数据和以点数表示的完成进度。 |
const sprint = await client.milestones.create({
project: proj.id,
name: "Sprint 1",
estimated_start: "2026-01-01",
estimated_finish: "2026-01-14",
});
const burndown = await client.milestones.stats(sprint.id);
分页
list() 始终返回响应的第一页。遍历完整结果集有三种方式:
// 1. Async iterator (recommended): lazy, walks the next links.
for await (const story of client.userStories.paginate({ project: 42 })) {
await processStory(story);
}
// 2. A specific page plus metadata.
const page = await client.userStories.listPage({ project: 42 }, 3);
console.log(page.data, page.pagination); // { count, perPage, current, next, prev }
// 3. Disable pagination entirely (returns the full set as one array).
const all = await client.http.get("/userstories", {
query: { project: 42 },
disablePagination: true,
});
⚠️
disablePagination会发送x-disable-pagination: True标头。仅在小型集合上使用此选项——对于大型集合,Taiga 可能会返回错误或超时。
错误
所有错误都继承自抽象类 TaigaError:
| 类 | 触发条件 | 有用字段 |
|---|---|---|
TaigaApiError | 任何不成功的 4xx/5xx 响应 | status、code、detail、body |
TaigaAuthError | 401 / 403 | + TaigaApiError 的所有字段 |
TaigaRateLimitError | 429 | + retryAfter(秒数,从 Retry-After 解析) |
TaigaNetworkError | 网络故障、中止、超时 | cause(底层的 fetch / AbortError) |
import {
TaigaApiError,
TaigaAuthError,
TaigaRateLimitError,
TaigaNetworkError,
} from "taiga-api-client";
try {
await client.projects.get(99999);
} catch (err) {
if (err instanceof TaigaRateLimitError) {
await sleep((err.retryAfter ?? 1) * 1000);
} else if (err instanceof TaigaAuthError) {
// Session is dead — log in again
} else if (err instanceof TaigaApiError) {
console.error(`Taiga ${err.status}: ${err.detail ?? err.message}`);
} else if (err instanceof TaigaNetworkError) {
// Network / timeout
}
}
乐观并发控制(OCC)
Taiga 通过 version 字段防止并发用户之间的更新丢失。调用 patch() 时,传入随对象收到的版本号——如果其他人先更新了该对象,Taiga 会返回 400 / 412,你便能得知发生了冲突。
const story = await client.userStories.get(42);
const updated = await client.userStories.patch(
story.id,
{ subject: "Renamed" },
story.version, // ← OCC
);
底层访问
对于尚未封装为资源的端点,可直接使用 client.http:
const result = await client.http.get<{ id: number }[]>("/wiki", {
query: { project: 1 },
});
console.log(result.data, result.pagination, result.headers);
可用的方法包括 get、post、put、patch、delete。它们都接受相同的 RequestOptions:{ query, body, headers, signal, disablePagination, skipAuth }。
覆盖范围
已实现:
- 身份验证:登录、刷新、注销、应用令牌、恢复会话、自动刷新并去重
- 用户:个人资料、列表、更新、更改密码、统计信息
- 项目:CRUD、按 slug 查询、统计信息、模块配置
- 成员关系:CRUD、bulk_create、重新发送邀请
- 用户故事:CRUD、bulk_create、对每个看板执行 bulk_update_order、filters_data
- 任务:CRUD、bulk_create、filters_data
- 问题:CRUD、filters_data、投票者、关注者、赞成投票/反对投票、关注/取消关注
- 里程碑:CRUD、统计信息
- 史诗 (v0.2 中新增):CRUD、bulk_create、filters_data、关联的用户故事(列出/添加/移除)、投票者、关注者、赞成投票/反对投票、关注/取消关注
尚未实现(计划在后续版本中推出): wiki、webhook、附件、自定义属性、历史记录/评论、导入器(Trello/Jira/GitHub)、项目模板、GitHub OAuth 流程。
如果你需要的端点不在覆盖范围内,请改用 client.http 和/或提交 issue。
兼容性
| 运行时 | 最低版本 | 备注 |
|---|---|---|
| Node.js | 18 | 全局 fetch 在 Node 18 中引入。 |
| Bun | 1.0 | 完全支持。 |
| Deno | 1.x | 应可正常运行(提供全局 fetch)。 |
| 浏览器 | 任意现代浏览器 | 需要在 Taiga 端配置 CORS。 |
许可证
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。