Skip to content

fix(tasks): unwrap inner "fields" key when parsing tasks.task.getFields - #4

Open
anticafeformat-dev wants to merge 1 commit into
stemirkhan:mainfrom
anticafeformat-dev:fix/parse-task-fields-unwrap
Open

anticafeformat-dev wants to merge 1 commit into
stemirkhan:mainfrom
anticafeformat-dev:fix/parse-task-fields-unwrap

Conversation

@anticafeformat-dev

Copy link
Copy Markdown

Problem

Bitrix REST returns tasks.task.getFields as:

{
  "result": {
    "fields": {
      "ID": {...},
      "TITLE": {...},
      "DEADLINE": {...},
      "PRIORITY": {...},
      ...
    }
  }
}

After callApi strips the outer result, parseTaskFields receives {fields: {...}} — but it iterates the top-level entries and treats fields itself as the only field. The inner value is the real field map (a large nested object), so the existing if (!value || typeof value !== "object" || Array.isArray(value)) check passes — and the parser silently produces {fields: {ID:..., TITLE:..., ...}} with one top-level key.

The downstream effect lands in the MCP adapter createTask.ts, which calls getTaskFields() to build the allowed-field set for input validation:

const availableFields = await service.getTaskFields();
const unknownFields = Object.keys(parsed.data.fields).filter(
  (field) => !(field in availableFields)
);

availableFields ends up as {fields: {...}} — the only allowed name is fields. Any real Bitrix field passed by the caller (DEADLINE, PRIORITY, PARENT_ID, START_DATE_PLAN, etc.) hits the unknownFields branch and is rejected with Unknown task fields. This makes the create_task tool's fields parameter effectively unusable for setting deadlines or any other field beyond the four flat ones (title, description, responsible_id, group_id).

Fix

Unwrap the inner fields key when present, so iteration runs over the actual field map:

let source: Record<string, unknown> = record;
const inner = record.fields;
if (inner && typeof inner === "object" && !Array.isArray(inner)) {
  source = inner as Record<string, unknown>;
}
// ...iterate Object.entries(source) instead of Object.entries(record)

The fallback (use record directly if no fields key) preserves backward compatibility for any future Bitrix portal that returns the flat shape.

Verification

$ get_task_fields
# Before: { "fields": { "fields": { "ID": {...}, "TITLE": {...}, ... } } }
#   → top-level keys count = 1 (only "fields"), parsing accepts but yields useless map
# After:  65 real Bitrix task fields enumerated correctly
#   (DEADLINE, PRIORITY, PARENT_ID, RESPONSIBLE_ID, GROUP_ID, START_DATE_PLAN, ... all visible)

$ create_task title="..." responsible_id=1 fields={DEADLINE: "2026-05-15T18:00:00+03:00", PRIORITY: "2"}
# Before: throws { "code": -32602, "message": "Unknown task fields", "data": {"unknownFields": ["DEADLINE", "PRIORITY"]} }
# After:  { "task_id": 142685 } — Bitrix task created with deadline and priority as requested

Note

This is independent from PRs #1 (capabilities shape), #2 (tools/call result envelope), and #3 (task field expansion in select). Four orthogonal fixes that arose during real-world MCP integration deployment. Can be merged in any order.

Bitrix REST returns `tasks.task.getFields` as `{result: {fields: {ID:..., TITLE:..., DEADLINE:..., ...}}}` (the field map is nested under a `fields` key inside `result`). After `callApi` strips the outer `result`, the parser receives `{fields: {...}}` — but `parseTaskFields` iterates the top-level entries and treats `fields` itself as the only field, throwing "Invalid task field definition" for the inner map.

The downstream effect is that `create_task`'s `fields` validation in the MCP adapter (which calls `getTaskFields()` to build the allowed-field set) sees only one available field name (`fields`), so any real Bitrix field name passed by the caller — `DEADLINE`, `PRIORITY`, `PARENT_ID`, etc. — fails with `Unknown task fields`.

This patch unwraps the `fields` key when present, so iteration runs over the actual field map.

## Verification

```
$ get_task_fields
# Before: { "fields": { "fields": { ID:..., TITLE:..., DEADLINE:..., ... } } }
#         (one top-level key, parsing breaks)
# After:  65 real Bitrix task fields visible

$ create_task title="..." responsible_id=1 fields={DEADLINE: "2026-05-15T18:00:00+03:00", PRIORITY: "2"}
# Before: throws "Unknown task fields: [DEADLINE, PRIORITY]"
# After:  task created with deadline + priority set as requested
```

This is independent from PRs stemirkhan#1 (capabilities shape), stemirkhan#2 (tools/call result envelope), and stemirkhan#3 (task field expansion) — all four are MCP/Bitrix integration fixes that arose during real-world deployment but each in a different code path. Can be merged in any order.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant