Skip to content

feat: add create_bulk_items tool - #351

Open
rami-monday wants to merge 4 commits into
masterfrom
feat/rami/create-bulk-items-tool
Open

feat: add create_bulk_items tool#351
rami-monday wants to merge 4 commits into
masterfrom
feat/rami/create-bulk-items-tool

Conversation

@rami-monday

Copy link
Copy Markdown
Collaborator

Summary

  • Adds create_bulk_items MCP tool that bulk creates/updates up to 10,000 items on a board via the ingest_items mutation (API-Version: 2026-07)
  • Handles full flow: GraphQL mutation → CSV generation → S3 upload → job status polling (2s interval, 60s timeout)
  • Supports optional upsert mode via on_match parameter

Test plan

  • Verify tool appears in MCP tool list
  • Test bulk create with simple items array
  • Test upsert mode with on_match parameter
  • Verify CSV escaping handles commas and quotes in values
  • Verify polling timeout behavior after 60s

🤖 Generated with Claude Code

rami-monday and others added 2 commits May 24, 2026 11:07
Adds a new MCP tool that bulk creates or updates up to 10,000 items
on a board using the ingest_items mutation (API-Version: 2026-07).
Handles CSV generation, S3 upload, and job status polling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Return type is UploadJobInit with fields: job_id, upload_url (not s3_url/status)
- Add group_id parameter (required by API, defaults to "topics")

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@omribz156

Copy link
Copy Markdown

I checked this against the current monday bulk-import docs, and one detail looks worth adjusting before merge: the docs recommend polling fetch_job_status about every 10 seconds, while this tool currently polls every 2 seconds.

Source: https://developer.monday.com/api-reference/docs/importing-items-in-bulk

Because ingest_items / backfill_items share a limit on starting jobs and the docs call out rate-limit handling, I would align POLL_INTERVAL_MS to 10000 unless the MCP server has a product reason to be more aggressive. Everything else in the mutation shape (API-Version: 2026-07, group_id, upload_url, job_id, and on_match.behaviour) matches what I found in the docs.

This review note was Codex-assisted; I manually checked it against the linked monday.com docs before posting.

Separates the single bulk tool into two distinct tools so LLMs can
better recognize update capabilities:
- create_bulk_items: creates new items (group_id optional, defaults to "topics")
- update_bulk_items: updates existing items via on_match (group_id required)

Shared logic extracted to bulk-items.utils.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
import { BaseMondayApiTool, createMondayApiAnnotations } from '../base-monday-api-tool';
import { boardIdSchema, groupIdSchema, itemsSchema, executeIngestItems } from './bulk-items.utils';

const onMatchSchema = z.object({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you can reference these tools from the existing change column value / create item tools in their description

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Added cross-references in both change_item_column_values ("For updating multiple items at once, use update_bulk_items instead") and create_item ("For creating many items at once, use create_bulk_items instead") descriptions.

};

export class UpdateBulkItemsTool extends BaseMondayApiTool<typeof updateBulkItemsSchema, never> {
name = 'update_bulk_items';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the name match the existing change column value tool?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer keeping update_bulk_items rather than change_item_column_values_bulk — the name was intentionally chosen for LLM discoverability. When an LLM sees "update multiple items" in a user request, update_bulk_items is a more natural match. The change_item_column_values naming is a legacy convention that's already confusing for models. That said, I've added cross-references in the existing tool descriptions so the LLM knows to route to bulk tools when appropriate.


const { job_id, upload_url } = ingestRes.ingest_items;

const csv = buildCsv(params.items);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it worth building the csv for 10 items? feels to me this solution fits huge uploads but not day-to-day updates. or is this the only existing API?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that for 5-10 items it's heavier than needed. However, ingest_items is the only bulk API available — the alternative is N individual change_item_column_values mutations which means N round-trips. Even for small batches, the CSV+S3 overhead is negligible (a few hundred bytes PUT) compared to the latency of multiple sequential GraphQL calls. We could add a threshold (e.g. <3 items → fall back to individual calls) but that adds branching complexity for marginal gain. Happy to revisit if we see performance issues in practice.

const { job_id, upload_url } = ingestRes.ingest_items;

const csv = buildCsv(params.items);
const uploadResponse = await fetch(upload_url, {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we return the url to the agent and instruct him to upload the file and then all us back again it will reduce the resources we use for this opteration.
the downside is that it's more work for the agent. WDYT?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting idea but I'd lean against it. Splitting into multiple tool calls means:

  1. The agent needs to know how to format CSV and PUT to S3 — that's fragile and model-dependent
  2. 3 tool calls minimum (get URL → upload → poll) instead of 1 atomic call
  3. More LLM tokens consumed per operation
  4. More failure points (agent might format CSV wrong, forget content-type header, etc.)

The resource cost on our side is one S3 PUT of a small payload + polling — both lightweight. Keeping it atomic gives us reliability and simplicity. If resource usage becomes a concern at scale we could revisit with a streaming approach, but for now the single-call UX is much more robust.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok just note that large file uploads can consume allot of resources (you basically pass all the payload twice).
maybe as an initial phase we should start we less than 10000 items counts

…n_values descriptions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

3 participants