Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
05c846f
Release 2.0.0b2
yakky Sep 12, 2026
dd92a78
fix(mcp): make project optional on list_milestones/list_wiki_pages
yakky Sep 14, 2026
b0043c2
docs: add evaluation report and activity log for project-param fix
yakky Sep 14, 2026
0b91d67
feat(mcp): add include_user_stories option to list_milestones/get_mil…
yakky Sep 14, 2026
26404b5
refactor(mcp): narrow _strip_user_stories type hint, add direct unit …
yakky Sep 14, 2026
c3b7b8c
docs(mcp): document include_user_stories flag on milestone tools
yakky Sep 14, 2026
630f41b
docs: add evaluation report and activity log for milestone user_stori…
yakky Sep 14, 2026
1547d81
Release 2.0.0b3
yakky Sep 14, 2026
75ac86f
chore: remove files from git
yakky Sep 16, 2026
e2a177d
feat(mcp): add strip_avatar_fields helper
yakky Sep 16, 2026
3b8afea
feat(mcp): add select_fields projection helper
yakky Sep 16, 2026
ec13d96
feat(mcp): add collapse_extra_info helper
yakky Sep 16, 2026
692f6c9
feat(mcp): add MINIMAL_FIELDS and apply_payload composition
yakky Sep 16, 2026
90e4485
feat(mcp): add payload/fields/strip_media/expand to project and membe…
yakky Sep 16, 2026
486c52a
feat(mcp): add payload/fields/strip_media/expand to user story tools
yakky Sep 16, 2026
3cbc87c
feat(mcp): add payload/fields/strip_media/expand to task tools
yakky Sep 16, 2026
dcc0f9f
feat(mcp): add payload/fields/strip_media/expand to issue tools
yakky Sep 16, 2026
3e7f1a3
feat(mcp): add payload/fields/strip_media/expand to epic tools
yakky Sep 16, 2026
11f5466
feat(mcp): add payload/fields/strip_media/expand to milestone tools
yakky Sep 16, 2026
ae66e47
feat(mcp): add payload/fields/strip_media/expand to wiki page tools
yakky Sep 16, 2026
98fa4aa
feat(mcp): add _resolve_assigned_users helper
yakky Sep 16, 2026
01d61e6
feat(mcp): add resolve_assigned_users to user story tools
yakky Sep 16, 2026
517f852
feat(mcp): add strict_filters to list tools
yakky Sep 16, 2026
d8e631b
docs(mcp): document payload/fields/strip_media/expand/resolve_assigne…
yakky Sep 16, 2026
53269f7
fix(mcp): type payload as Literal, include assigned_users_extra_info …
yakky Sep 16, 2026
445e579
docs(mcp): compress payload-param docstrings, point to docs/mcp.rst
yakky Sep 16, 2026
3c6d703
docs(mcp): restore minimal-field-set enumeration lost in docstring co…
yakky Sep 16, 2026
0d0544c
docs(mcp): add docstring to _select_from_item
yakky Sep 16, 2026
cc42e62
docs(changes): add towncrier fragment for payload-reduction feature
yakky Sep 16, 2026
9979251
Bump develop version [ci skip]
yakky Sep 17, 2026
86a4d49
feat(mcp): add _represent helper for return_representation
yakky Sep 17, 2026
8ffc78d
feat(mcp): add return_representation to user story write tools
yakky Sep 17, 2026
3310f7f
feat(mcp): add return_representation to task write tools
yakky Sep 17, 2026
b0e2f5a
feat(mcp): add return_representation to issue write tools
yakky Sep 17, 2026
a87a816
feat(mcp): add return_representation to epic write tools
yakky Sep 17, 2026
77a6498
feat(mcp): add return_representation to create_milestone
yakky Sep 17, 2026
4f01f46
feat(mcp): add return_representation to wiki page write tools
yakky Sep 17, 2026
1411c54
feat(mcp): add update_work_items batch-write tool
yakky Sep 17, 2026
5fb893a
fix(mcp): keep update_work_items per-item isolation for malformed items
yakky Sep 17, 2026
aa3ceb0
docs(mcp): document return_representation and update_work_items
yakky Sep 17, 2026
02bfb94
fix(mcp): correct _represent merge order, scope version doc claim, im…
yakky Sep 17, 2026
519d296
docs(changes): add towncrier fragment for write-response payload-redu…
yakky Sep 17, 2026
8b8abe9
fix(mcp): read membership display name from full_name, not full_name_…
yakky Sep 17, 2026
4a3c642
docs(changes): add towncrier fragment for resolve_assigned_users fiel…
yakky Sep 17, 2026
b77139a
fix(mcp): improve minimal-field-set accuracy for milestone/userstory/…
yakky Sep 17, 2026
07f33e2
docs(changes): add towncrier fragment for M4/M5/M8/M11 minimal-field-…
yakky Sep 17, 2026
a3f8949
docs(mcp): document strict_filters' limits prominently
yakky Sep 17, 2026
cde1ece
Release 2.0.0.b5
yakky Sep 17, 2026
060e3a4
fix(mcp): surface real error messages instead of a generic wrapper (N3)
yakky Sep 17, 2026
4901a62
fix(mcp): collapse invited_by under payload=compact/minimal (M7)
yakky Sep 17, 2026
40b8f94
fix(mcp): strip media from expand'd blocks too (M6)
yakky Sep 17, 2026
be46681
docs(mcp): document a client-side 'default means required' validation…
yakky Sep 17, 2026
c1b98f8
docs(mcp): document that embedded user_stories lack assigned_users (N4)
yakky Sep 17, 2026
f006e7b
docs(mcp): document that a list-valued filter silently drops all but …
yakky Sep 17, 2026
2db7322
chore: release 2.0.0b6
yakky Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/+mcp-minimal-field-set-fixes.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the MCP server's `minimal` payload projection: milestones now include `project_extra_info.name`/`.slug`, user stories now include the bare `assigned_users` id list, and both user stories and issues drop the redundant per-project `status` id in favour of the cross-project-comparable `status_extra_info.name`.
1 change: 1 addition & 0 deletions changes/+mcp-payload-reduction.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add opt-in payload-reduction parameters (`payload`, `fields`, `strip_media`, `expand`, `resolve_assigned_users`, `strict_filters`) to the MCP server's read tools, shrinking oversized responses on request while leaving default behaviour byte-for-byte unchanged.
1 change: 1 addition & 0 deletions changes/+mcp-resolve-assigned-users-field-name-fix.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `resolve_assigned_users` always returning `null` display names by reading the correct `full_name` field from Taiga membership records instead of the nonexistent `full_name_display`.
1 change: 1 addition & 0 deletions changes/+mcp-write-payload-reduction.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add opt-in `return_representation` (`full`/`minimal`/`none`) to the MCP server's write tools, and a new `update_work_items` batch-write tool, shrinking write-response payloads and collapsing many-item write sequences into one call while leaving default behaviour byte-for-byte unchanged.
206 changes: 203 additions & 3 deletions docs/mcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,28 @@ not in any particular project. Check it went through with:

claude mcp get taiga

.. warning:: **Some MCP clients require every declared parameter to be passed
explicitly, even ones with a documented default.** Every
parameter below that shows a default (e.g. ``payload="full"``,
``strict_filters=False``) is genuinely optional in this server's
own JSON schema and its runtime validation - confirmed by
calling the real server directly over the MCP protocol,
omitting those parameters entirely. Some MCP clients have
nonetheless been observed rejecting the call outright
(``-32602``, wording resembling Zod's ``nonoptional`` schema
check) when a parameter carrying a schema-level ``default`` is
omitted - for *every* such parameter, not just the ones this
server added recently: `include_user_stories` on
``list_milestones`` (which predates this server's payload-
reduction work) triggers the same rejection on an affected
client. This is a property of that client's own schema
validation, not of this server, and there is no available hook
in this server's dependencies to change what gets emitted to
work around it. **If your client exhibits this, pass every
parameter explicitly on every call to an affected tool** -
there is no way to make a client-side check like this
optional from the server side.

****************
Available tools
****************
Expand Down Expand Up @@ -219,6 +241,10 @@ Available tools
Link a user story to an epic, identifying both by their per-project ref
numbers (primary) or by database id (secondary, see below).

``update_work_items``
Update a batch of user stories/tasks/issues/epics in one call - see the
tip below for the exact shape and its non-atomic semantics.

.. important:: ``get_user_story``/``get_task``/``get_issue``/``get_epic`` and
their ``update_*``/``delete_*`` counterparts take a ``project`` (id
or slug) and a ``ref`` - the per-project sequential number Taiga
Expand All @@ -237,11 +263,23 @@ Available tools
you already hold the database id (for example from a prior tool
response), not a ref.

``list_milestones``, ``get_milestone``, ``create_milestone``, ``delete_milestone``
Manage milestones (sprints).
``list_milestones``, ``get_milestone``
List/get milestones (sprints), optionally scoped to a project (``list_milestones``
only). Each milestone embeds its full ``user_stories`` - pass
``include_user_stories=False`` to strip that (potentially large) field from the
result. This embedded route is the cheapest way to get every story in a sprint in
one call (pair it with ``fields=["id", "user_stories.ref", ...]`` to shrink it
further), but Taiga's embedded story serializer carries only the single primary
``assigned_to`` - it has no ``assigned_users`` key at all, so multi-assignee data is
unavailable through this route regardless of ``fields``/``resolve_assigned_users``.
For complete multi-assignee data, fetch those stories individually with
``list_user_stories``/``get_user_story`` instead.

``create_milestone``, ``delete_milestone``
Create/delete milestones (sprints).

``list_wiki_pages``, ``get_wiki_page``, ``create_wiki_page``, ``update_wiki_page``
Manage wiki pages.
Manage wiki pages, optionally scoped to a project.

.. tip:: Call ``get_project`` first when creating or updating an entity - it
returns every status/priority/severity/points id valid for that
Expand All @@ -252,6 +290,168 @@ Available tools
through further pages, and ``order_by`` (e.g. ``-created_date``) to
control ordering - for example to fetch the most recent items first.

.. tip:: Every ``list_*``/``get_*`` tool that returns a full resource - projects,
user stories, tasks, issues, epics, milestones, memberships, wiki pages -
accepts four further parameters to shrink an oversized response, all
opt-in (the default reproduces today's full response exactly):

- ``payload``: ``"full"`` (default, untouched) / ``"compact"`` (drops
avatar/logo URLs and shrinks every ``*_extra_info`` block to its id
plus display name) / ``"minimal"`` (only the fields a sprint-planning
report actually reads - currently defined for user stories, issues,
epics and milestones; for any other resource ``"minimal"`` behaves
the same as ``"compact"`` for now). The exact field set kept by
``payload="minimal"`` per entity (``MINIMAL_FIELDS`` in
``taiga/mcp_server/serialize.py``):

- ``milestone`` (``list_milestones``/``get_milestone``): ``id``,
``name``, ``slug``, ``project``, ``project_extra_info.name``,
``project_extra_info.slug``, ``estimated_start``,
``estimated_finish``, ``closed``. The board's name/slug are
included alongside the bare ``project`` id specifically so a
cross-project report can display and link each board without a
second call.
- ``userstory`` (``list_user_stories``/``get_user_story``/
``get_user_story_by_id``): ``id``, ``ref``, ``subject``,
``version``, ``milestone``, ``milestone_name``,
``status_extra_info.name``, ``status_extra_info.is_closed``,
``is_closed``, ``finish_date``, ``is_blocked``,
``assigned_to_extra_info.full_name_display``,
``assigned_users``, ``epics.ref``, ``assigned_users_extra_info``.
The numeric ``status`` id is deliberately omitted - it is a
per-project id, not comparable across boards, and redundant
alongside ``status_extra_info.name``. ``assigned_users`` (the
bare secondary-assignee id list) is included so ``minimal``
never silently under-reports who is assigned; pair it with
``resolve_assigned_users=True`` for names, not just ids.
- ``issue`` (``list_issues``/``get_issue``/``get_issue_by_id``):
same as ``userstory`` minus ``assigned_users``/
``assigned_users_extra_info`` (issues have no ``assigned_users``).
- ``epic`` (``list_epics``/``get_epic``/``get_epic_by_id``):
``id``, ``ref``, ``subject``, ``status_extra_info.name``,
``project``.
- ``fields``: an explicit list of field paths, overriding ``payload``
entirely, e.g. ``["ref", "subject", "status_extra_info.name"]``. A
dotted path keeps only that nested key; if the value at that point is
itself a list (e.g. a story's ``epics``), the remaining path is
applied to every element, e.g. ``"epics.ref"``.
- ``strip_media``: ``True``/``False``, overriding whether avatar/logo
URLs (``photo``, ``big_photo``, ``gravatar_id``, ``logo_small_url``)
are stripped, regardless of ``payload``. These carry rotating
signed-URL signatures, so leaving them in also defeats prompt caching
between otherwise-identical calls.
- ``expand``: a list of top-level block names to add back at full
detail on top of a reduced ``payload``, e.g.
``payload="minimal", expand=["assigned_to_extra_info"]``.

``list_user_stories``, ``get_user_story`` and ``get_user_story_by_id``
additionally accept ``resolve_assigned_users=True``: ``assigned_users``
is a bare list of user ids with no names anywhere in the default
payload, so this resolves them into
``assigned_users_extra_info: [{"id", "full_name_display"}, ...]`` via
that story's project memberships. Off by default because, unlike the
four parameters above, it adds a request rather than removing one (one
``list_memberships`` call per distinct project touched).

Every ``list_*`` tool additionally accepts ``strict_filters=False``
(default): pass ``True`` to raise immediately if ``filters`` nests one
of this server's own parameter names by mistake (e.g.
``filters={"include_user_stories": False}``) instead of silently
returning an unfiltered response - Taiga's REST backend ignores
unknown query parameters with no error either way, which measured as
much as a 24x size regression with no signal that anything went wrong.

**`strict_filters` is a narrow guard, not a filter validator.** It
only catches this server's own parameter names appearing inside
`filters` by mistake. It gives **no protection** against a
misspelled or unsupported Taiga filter key (e.g. `milestone__in`,
which Taiga silently ignores rather than erroring - see the note
below) - that class of mistake returns a normal-looking but wrong
result set with no error either way, `strict_filters` or not. Any
filter-based narrowing this server accepts must be re-asserted
client-side (e.g. checking the returned items' own fields match
what the filter was supposed to select) rather than trusted purely
because the call didn't raise.

.. note:: ``filters`` is forwarded as-is to Taiga's REST endpoint, so
server-side filtering - e.g. ``list_milestones(filters={"closed":
False, "estimated_start__lte": "2026-09-20"})`` - already works
today with no MCP-side change, for any lookup Taiga's own API
filter backend supports. Which lookups that includes is a property
of the Taiga server you're talking to, not of this client. Filters
verified against a live instance: ``closed`` (milestones),
``page_size`` (all ``list_*`` tools, caps at 100 per page regardless
of the value requested), ``estimated_start__lte``/
``estimated_finish__gte`` (milestones - returns exactly the boards
whose window overlaps the given range), and ``milestone`` as a
single int (user stories, issues). A comma-separated list of ids is
**not** supported the same way for either ``milestone`` (errors) or
``milestone__in`` (silently ignored, returning an arbitrary unrelated
page of results rather than an error) - do not rely on either form;
fetch each milestone's items with its own call instead. A Python
*list* value (e.g. ``filters={"milestone": [1444, 1446]}``) is a
third failure mode, and the most dangerous of the three: it returns
a small, clean, plausible-looking result set - but only for the
*last* id in the list, with every other id's items silently
dropped. This client sends the list correctly (as repeated query
parameters, standard ``requests`` behaviour); the collapse happens
in Taiga's own REST backend, which appears to read only the last
value of a repeated parameter (standard Django ``QueryDict.get()``
semantics) - nothing on this client's side can change that. Do not
pass a list as a filter value for any key; fetch each id with its
own call instead. Separately,
``project=None`` (the default on item-listing tools) already returns
results across every project the caller can see - no project scope
is required for a cross-project item query.

.. note:: Taiga's own data can disagree with itself on closure state: the
top-level ``is_closed`` field and ``status_extra_info.is_closed``
are two independently-set signals and have been observed to
contradict each other on the same item (e.g. ``is_closed: true``
while the status is actually "In progress" and
``status_extra_info.is_closed: false``). This is a property of the
underlying Taiga data, not a serialization defect in this server.
``status_extra_info.name``/``status_extra_info.is_closed`` are the
more reliable signals if the two disagree - they come directly from
the status Taiga's UI itself displays, rather than a separately
maintained flag on the item.

.. tip:: Every ``create_*``/``update_*`` tool accepts ``return_representation``
(``"full"`` default / ``"minimal"`` / ``"none"``), opt-in, to shrink
what a write echoes back:

- ``"full"`` (default): the complete written resource, exactly as
before.
- ``"minimal"``: ``{"id", "ref" (only if the entity has one),
"version", ...the fields you passed in ``fields``}`` - never the
tool's own required arguments (``subject``, ``status``, etc.),
since you already know those - only ``id``/``ref``/``version`` are
genuinely new information a write produces.
- ``"none"``: ``{"ok": true, "id", "ref" (if any), "version"}`` - a
bare acknowledgement.

On ``update_*`` tools, ``"minimal"``/``"none"`` also skip the
re-fetch these tools otherwise perform after writing - a latency
win, not just a smaller response. ``version`` is not a separate
parameter on any ``create_*``/``update_*`` tool: if Taiga needs it
for optimistic locking, pass it inside ``fields`` yourself, exactly
as today. (``set_custom_attribute_value``/``set_custom_attribute_value_by_id``
are the exception - see the note above on their own, unrelated
``version`` sequence.)

.. tip:: ``update_work_items(project, updates, return_representation="full")``
updates a batch of user stories/tasks/issues/epics in one call.
Each entry in ``updates`` is
``{"entity_type": "user_story"|"task"|"issue"|"epic", "ref": <int>,
"fields": {...}}``. **Not atomic** - items are processed in order,
each succeeds or fails independently, and one failure never rolls
back or blocks any other item. Returns one result row per input
item, in the same order (zip ``updates`` with the result to match
them up); a failed item's row is
``{"status": "error", "entity_type", "ref", "error"}``. Wiki pages
aren't supported here (no per-project ``ref``) - use
``update_wiki_page`` directly.

****************
Security notes
****************
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ commit = true
message = "Release {new_version}"
commit_args = "--no-verify"
tag = false
current_version = "1.3.4.dev1"
current_version = "2.0.0b6"
parse = """(?x)
(?P<major>[0-9]+)
\\.(?P<minor>[0-9]+)
Expand Down
2 changes: 1 addition & 1 deletion taiga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Taiga Python API library
"""

__version__ = "1.3.4.dev1"
__version__ = "2.0.0.b6"
__author__ = "Nephila"
__license__ = "MIT"
__all__ = ["TaigaAPI"]
Expand Down
Loading
Loading