io.github.xHisusx/taiga-mcp-server

السجل الرسمي أخرى

Read and manage your own Taiga projects: items, statuses, assignees, sprints, comments

التثبيت

أضف هذا إلى إعدادات MCP في عميلك (Claude Desktop: claude_desktop_config.json، وCursor: ‏.cursor/mcp.json، ويستخدم VS Code وغيره الصيغة نفسها «mcpServers»).

NPM package taiga-api-mcp-server
Claude Code
claude mcp add taiga-mcp-server -e 'TAIGA_URL=<TAIGA_URL>' -e 'TAIGA_USERNAME=<TAIGA_USERNAME>' -e 'TAIGA_PASSWORD=<your taiga_password>' -e 'TAIGA_APP_TOKEN=<your taiga_app_token>' -e 'TAIGA_PROJECT=<TAIGA_PROJECT>' -e 'TAIGA_MCP_READ_ONLY=0' -e 'TAIGA_MCP_ALLOW_DELETE=0' -- npx -y taiga-api-mcp-server@0.5.0
إعداد JSON (Claude Desktop وCursor وVS Code)
{
    "mcpServers": {
        "taiga-mcp-server": {
            "command": "npx",
            "args": [
                "-y",
                "taiga-api-mcp-server@0.5.0"
            ],
            "env": {
                "TAIGA_URL": "<TAIGA_URL>",
                "TAIGA_USERNAME": "<TAIGA_USERNAME>",
                "TAIGA_PASSWORD": "<your taiga_password>",
                "TAIGA_APP_TOKEN": "<your taiga_app_token>",
                "TAIGA_PROJECT": "<TAIGA_PROJECT>",
                "TAIGA_MCP_READ_ONLY": "0",
                "TAIGA_MCP_ALLOW_DELETE": "0"
            }
        }
    }
}

متغيرات البيئة

  • TAIGA_URL إلزامي Taiga instance root, without /api/v1. Taiga Cloud: https://api.taiga.io
  • TAIGA_USERNAME Taiga username or email, used together with TAIGA_PASSWORD
  • TAIGA_PASSWORD سري Password for TAIGA_USERNAME
  • TAIGA_APP_TOKEN سري Long-lived Application token, instead of a username and password
  • TAIGA_PROJECT Default project (id, slug or name) for tool calls that omit one
  • TAIGA_MCP_READ_ONLY Set to 1 to register the read tools only
  • TAIGA_MCP_ALLOW_DELETE Set to 1 to also register taiga_delete_item

التوثيق

مأخوذ من ملف README للمشروع على GitHub ويُحدَّث أسبوعيًا. تفتح الروابط على GitHub.

المحتويات
  1. taiga-api-client
  2. Installation
  3. Quick start
  4. 🤖 MCP server: Taiga for AI agents
  5. 💡 Looking up catalog IDs? Use filters_data
  6. Creating the client
  7. Authentication
  8. Resources and methods
  9. Pagination
  10. Errors
  11. Optimistic Concurrency Control (OCC)
  12. Low-level access
  13. Coverage
  14. Compatibility
  15. License

taiga-api-client

🇬🇧 English version — you are here · 🇷🇺 Русская версия

A TypeScript client for the Taiga REST API. Zero runtime dependencies, dual ESM + CJS, runs on Node ≥18, Bun ≥1.0 and any environment with a global fetch.

  • 🔐 Bearer authentication with automatic token refresh and deduplication of concurrent refreshes
  • 📦 Fully typed: types for every resource, payload and parameter
  • 🚫 Zero runtime dependencies
  • 🔄 Async iterators for pagination
  • ⚠️ Typed errors (TaigaApiError, TaigaAuthError, TaigaRateLimitError, TaigaNetworkError)
  • 🚀 Dual ESM + CJS build with .d.ts / .d.cts type maps

Installation

npm install taiga-api-client
# or
bun add taiga-api-client
# or
pnpm add taiga-api-client

Quick start

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: "alice@example.com", 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 server: Taiga for AI agents

This repository also ships taiga-api-mcp-server — a Model Context Protocol server built on this client. Point an MCP client (Claude Code, Claude Desktop, Cursor, …) at your Taiga account and it can read what is assigned to you, move items between statuses, create user stories, tasks, issues and epics, assign people, estimate, comment and plan sprints — with your own permissions and nothing more.

Statuses, members and sprints are addressed by name; the server resolves them against the project's own catalogs, so an agent never has to guess a numeric ID.

claude mcp add taiga   --env TAIGA_URL=https://api.taiga.io   --env TAIGA_USERNAME=you@example.com   --env TAIGA_PASSWORD=...   -- npx -y taiga-api-mcp-server

📖 Setup, tool reference and examples: mcp/README.md

💡 Looking up catalog IDs? Use filters_data

Most write operations need numeric IDs of project-specific catalog values (status, type, severity, priority, assignee, …). Don't fetch each catalog separately — every resource exposes a filters_data method that returns all of them in one request, scoped to the project.

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 });

📖 Read the full guide: docs/filters-data.md — caching, available endpoints per resource, when it is not enough.

Creating the client

Minimum:

const client = new TaigaClient({ baseUrl: "https://api.taiga.io" });

baseUrl is the only required option. Everything else is optional.

Full example:

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
OptionRequired?TypeDefaultDescription
baseUrl✅ yesstring—Root URL of your Taiga instance (without /api/v1). For Taiga Cloud: https://api.taiga.io.
tokenno`string \null`nullBearer token. Pass it to restore an existing session.
refreshTokenno`string \null`nullPaired refresh token.
applicationTokenno`string \null`nullStatic Application token (mutually exclusive with bearer).
autoRefreshnobooleantrueAutomatically refresh the bearer token on 401.
timeoutMsnonumber30000Per-request timeout in milliseconds.
userAgentnostring—User-Agent header.
acceptLanguagenostring—Accept-Language header (i18n of response strings).
apiPrefixnostring/api/v1API path prefix. Change only for non-standard self-hosted setups.
fetchnotypeof fetchglobalCustom fetch implementation (proxy, mocking, MSW).
onTokenChangeno`(tokens \null) => void \Promise`—Callback fired after login and after a successful auto-refresh. Use it to persist tokens.

Authentication

Three modes are supported:

ModeWhen to useHeader
Bearer + refreshStandard interactive loginAuthorization: Bearer X
Session restoreProcess restart — pass saved tokens to the constructorAuthorization: Bearer X
Application tokenIntegrations / bots, long-lived static tokenAuthorization: Application X
Automatic token refresh

With autoRefresh: true (the default), the client transparently intercepts 401 responses, calls POST /auth/refresh, and retries the original request once. Concurrent 401s are deduplicated — /auth/refresh is called exactly once even if 10 requests fail simultaneously. If the refresh itself fails, tokens are cleared and a TaigaAuthError is thrown.

Persisting tokens across process restarts
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 is invoked:

  • after client.auth.login(...) — with the new token pair;
  • after a successful auto-refresh — with the new pair;
  • after client.auth.logout() — with null.

The returned Promise is awaited before the request is retried, so your persistence is guaranteed to complete before the next request goes out.

Resources and methods

Every resource extends BaseResource, which provides standard CRUD. Specific resources add their own methods on top.

BaseResource<T> — common CRUD
MethodHTTPReturnsDescription
list(params?)GET /resourceT[]First page (or the entire response when disablePagination).
paginate(params?)GET /resource?page=NAsyncIterable<T>Lazy async iterator that walks x-pagination-next links.
listPage(params?, page?)GET /resource?page=N{ data, pagination }A specific page plus pagination metadata.
get(id)GET /resource/:idTA single resource by ID.
create(payload)POST /resourceTCreate.
update(id, payload)PUT /resource/:idTFull replace. Requires every required field.
patch(id, payload, version?)PATCH /resource/:idTPartial update. version enables optimistic concurrency control.
delete(id)DELETE /resource/:idvoidDelete.
client.auth — authentication
MethodReturnsDescription
login({ username, password })Promise<AuthResponse>POST /auth. username accepts both username and email.
refresh()Promise<string>Manual refresh. Usually invoked automatically on 401.
logout()Promise<void>Clears tokens locally and calls onTokenChange(null).
`setTokens(tokens \null)`Promise<void>Set tokens manually (e.g. after restoring from a database).
getTokens()`AuthTokens \null`Current in-memory tokens.
getCurrentUser()`AuthUser \null`The user profile from the last successful login().
onTokenChange(handler)voidReplace the persistence callback at runtime.
const auth = await client.auth.login({ username: "u", password: "p" });
console.log(auth.auth_token, auth.refresh, auth.full_name);
client.users — users

Base CRUD plus:

MethodHTTPDescription
me()GET /users/meThe currently authenticated user.
updateMe(payload)PUT /users/:id (own id)Convenience shortcut for update(me.id, …).
changePassword(currentPassword, newPassword)POST /users/change_passwordChange the password.
stats(id)GET /users/:id/statsUser statistics.

⚠️ users.create() intentionally throws — registration uses dedicated endpoints (out of scope in 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 — projects

Base CRUD plus:

MethodHTTPDescription
getBySlug(slug)GET /projects/by_slug?slug=...Fetch a project by slug instead of id.
stats(id)GET /projects/:id/statsProject statistics (points, milestones).
modulesConfig(id)GET /projects/:id/modulesIntegrations config (GitHub, GitLab, Bitbucket, Gogs).
setModulesConfig(id, config)PATCH /projects/:id/modulesUpdate integrations config.
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 — project memberships

Base CRUD plus:

MethodHTTPDescription
bulkCreate(payload)POST /memberships/bulk_createInvite multiple users in one call.
resendInvitation(id)POST /memberships/:id/resend_invitationResend the invitation email.
await client.memberships.bulkCreate({
  project_id: proj.id,
  bulk_memberships: [
    { role_id: 1, username: "alice@example.com" },
    { role_id: 2, username: "bob@example.com" },
  ],
  invitation_extra_text: "Welcome to the team",
});
client.userStories — user stories

Base CRUD plus:

MethodHTTPDescription
bulkCreate(payload)POST /userstories/bulk_createCreate multiple stories from a newline-separated string.
bulkUpdateBacklogOrder(payload)POST /userstories/bulk_update_backlog_orderReorder stories in the backlog.
bulkUpdateKanbanOrder(payload)POST /userstories/bulk_update_kanban_orderReorder stories on the kanban board.
bulkUpdateSprintOrder(payload)POST /userstories/bulk_update_sprint_orderReorder stories within a sprint.
filtersData({ project })GET /userstories/filters_data?project=...Available filters for the UI: statuses, tags, assignees.
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 — tasks

Base CRUD plus:

MethodHTTPDescription
bulkCreate(payload)POST /tasks/bulk_createCreate multiple tasks at once.
filtersData({ project })GET /tasks/filters_data?project=...Available filters.
const task = await client.tasks.create({
  project: proj.id,
  subject: "Implement OAuth callback",
  user_story: story.id, // link to a user story
});
client.issues — issues

Base CRUD plus:

MethodHTTPDescription
filtersData({ project })GET /issues/filters_data?project=...Available filters (types, severities, priorities).
voters(id)GET /issues/:id/votersUsers that have upvoted the issue.
upvote(id)POST /issues/:id/upvoteUpvote an issue.
downvote(id)POST /issues/:id/downvoteRemove your upvote.
watchers(id)GET /issues/:id/watchersUsers watching the issue.
watch(id)POST /issues/:id/watchSubscribe to notifications.
unwatch(id)POST /issues/:id/unwatchUnsubscribe.
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 — epics

Base CRUD plus:

MethodHTTPDescription
bulkCreate(payload)POST /epics/bulk_createCreate multiple epics from a newline-separated string.
filtersData({ project })GET /epics/filters_data?project=...Available filters for the UI.
relatedUserStories(id)GET /epics/:id/related_userstoriesUser stories linked to the epic.
addRelatedUserStory(id, { user_story })POST /epics/:id/related_userstoriesLink an existing user story to the epic.
removeRelatedUserStory(id, usId)DELETE /epics/:id/related_userstories/:usIdUnlink a user story from the epic.
voters(id)GET /epics/:id/votersUsers that have upvoted the epic.
upvote(id)POST /epics/:id/upvoteUpvote an epic.
downvote(id)POST /epics/:id/downvoteRemove your upvote.
watchers(id)GET /epics/:id/watchersUsers watching the epic.
watch(id)POST /epics/:id/watchSubscribe to notifications.
unwatch(id)POST /epics/:id/unwatchUnsubscribe.
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);
}

ℹ️ Epics must be enabled on the project (is_epics_activated: true) for these endpoints to work.

client.milestones — sprints

Base CRUD plus:

MethodHTTPDescription
stats(id)GET /milestones/:id/statsBurndown data and completion progress in points.
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);

Pagination

list() always returns the first page of the response. There are three ways to iterate the full set:

// 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 sends the x-disable-pagination: True header. Use it only on small collections — for large ones Taiga may respond with an error or time out.

Errors

Every error inherits from the abstract TaigaError:

ClassWhen it is thrownUseful fields
TaigaApiErrorAny unsuccessful 4xx/5xxstatus, code, detail, body
TaigaAuthError401 / 403+ everything from TaigaApiError
TaigaRateLimitError429+ retryAfter (seconds, parsed from Retry-After)
TaigaNetworkErrorNetwork failure, abort, timeoutcause (the underlying 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
  }
}

Optimistic Concurrency Control (OCC)

Taiga prevents lost updates between concurrent users via a version field. When calling patch(), pass the version you received with the object — if someone updated it first, Taiga responds with 400 / 412 and you learn about the conflict.

const story = await client.userStories.get(42);
const updated = await client.userStories.patch(
  story.id,
  { subject: "Renamed" },
  story.version, // ← OCC
);

Low-level access

For endpoints that are not yet wrapped as resources, use client.http directly:

const result = await client.http.get<{ id: number }[]>("/wiki", {
  query: { project: 1 },
});
console.log(result.data, result.pagination, result.headers);

The available methods are get, post, put, patch, delete. They all accept the same RequestOptions: { query, body, headers, signal, disablePagination, skipAuth }.

Coverage

Implemented:

  • Auth: login, refresh, logout, application token, session restore, auto-refresh with deduplication
  • Users: profile, list, update, change password, stats
  • Projects: CRUD, by_slug, stats, modules config
  • Memberships: CRUD, bulk_create, resend invitation
  • User Stories: CRUD, bulk_create, bulk_update_order for every board, filters_data
  • Tasks: CRUD, bulk_create, filters_data
  • Issues: CRUD, filters_data, voters, watchers, upvote/downvote, watch/unwatch
  • Milestones: CRUD, stats
  • Epics (new in v0.2): CRUD, bulk_create, filters_data, related_userstories (list/add/remove), voters, watchers, upvote/downvote, watch/unwatch

Not implemented yet (planned for upcoming releases): wiki, webhooks, attachments, custom attributes, history/comments, importers (Trello/Jira/GitHub), project templates, GitHub OAuth flow.

If the endpoint you need is not covered, fall back to client.http and/or open an issue.

Compatibility

RuntimeMinimumNotes
Node.js18Global fetch was introduced in Node 18.
Bun1.0Fully supported.
Deno1.xShould work (global fetch is available).
Browserany modern browserRequires CORS to be configured on Taiga's side.

License

MIT

الأسئلة الشائعة

ما هو io.github.xHisusx/taiga-mcp-server؟

Read and manage your own Taiga projects: items, statuses, assignees, sprints, comments

كيف أثبّت io.github.xHisusx/taiga-mcp-server؟

يعمل على جهازك. في Claude Code: claude mcp add taiga-mcp-server -e 'TAIGA_URL=<TAIGA_URL>' -e 'TAIGA_USERNAME=<TAIGA_USERNAME>' -e 'TAIGA_PASSWORD=<your taiga_password>' -e 'TAIGA_APP_TOKEN=<your taiga_app_token>' -e 'TAIGA_PROJECT=<TAIGA_PROJECT>' -e 'TAIGA_MCP_READ_ONLY=0' -e 'TAIGA_MCP_ALLOW_DELETE=0' -- npx -y taiga-api-mcp-server@0.5.0. وفي العملاء الآخرين، الصق إعداد JSON من هذه الصفحة في إعدادات MCP الخاصة بهم.

هل يحتاج io.github.xHisusx/taiga-mcp-server إلى مفتاح API؟

نعم. يتطلب TAIGA_URL, TAIGA_PASSWORD, TAIGA_APP_TOKEN، وتضبطها في إعداداته.

هل io.github.xHisusx/taiga-mcp-server مفتوح المصدر؟

نعم. شيفرته متاحة على GitHub بترخيص MIT.

من يصون io.github.xHisusx/taiga-mcp-server؟

ينشره xHisusx. يدرجه AI.tn لكنه لا يصونه ولا يوصي به.

من سجل MCP الرسمي. الإدراج ليس توصية: راجع شيفرة الخادم وصلاحياته قبل منحه الوصول إلى بياناتك.