MCP tools
Bid files and background jobs
MCP tools for generating the files you submit with a bid (the response workbook, the attachments file and the cover letter) and following the background jobs behind the large ones.
These tools produce the files you submit with a bid: the NUPCO response workbook, the NUPCO attachments file and the Arabic cover letter. Each file is built from what you have saved in the platform: the prices on the tender's items, the items marked as participating, the products selected for them and the quotations you generated. Four more tools follow the background jobs behind the files that take a while. The bid files explains how the same files are made from the app. For the offers on the iNUPCO portal itself, there is a step-by-step walkthrough.
priced_items_not_participating; `tender_item_participation_set_batch` puts the mark back. An item priced 0 is left out whatever its mark and counted as zero_priced_items, since a zero is not a bid price.The three tools take an optional quotation_ids; with it, the files cover and cite only the items those quotations quote. The saved bid prices stay the authority: a selected quotation that states a different price is reported, never used to change the file. Each tool also takes report: true to return a JSON report instead of the file, so use it first when you want to know what is still missing. The three tools belong to the Pro and Enterprise plans.
| Tool | What it does |
|---|---|
| tenders_nupco_response_fill | Fill the NUPCO response file |
| tenders_nupco_attachments_fill | Generate the NUPCO attachments file |
| tenders_cover_letter_pdf | Generate the tender cover letter |
| generation_jobs_list | List generation jobs |
| generation_jobs_get | Get a generation job |
| generation_jobs_part | Download a generation job part |
| generation_jobs_cancel | Cancel a generation job |
The three bid files
None of these tools changes your records: they read your saved prices and generate a file from them.
tenders_nupco_response_fill
Fills the response workbook you downloaded from NUPCO's e-portal with your saved prices, VAT and selected products for one tender, and returns the same workbook. The template must be the portal's own file for that tender: it cannot be made from scratch, and a file for another tender is refused.
Read-only · scope bids.read · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID, from tenders_list. |
template_base64 | string | Yes | The response workbook (.xlsx) exactly as downloaded from the NUPCO portal, as base64. About 3 MB is the practical ceiling, because the server refuses any request over about 4.5 MB. On WhatsApp the assistant passes the attachment's reference in its place. |
filename | string | No | The original file name, up to 300 characters. |
vat_percent | number | No | The default VAT percentage, 0 to 100, for rows whose selected product sets none. 15 when omitted. A product's own rate always wins. |
quotation_ids | integer[] | No | The quotation IDs, from `tender_quotations_list`, normally the approved run. The file covers only the items those quotations quote and cites exactly them. Every ID must be one of your organization's quotations on this tender, or the call answers 400 quotation_tender_mismatch. From 1 to 5,000 IDs. Omit it to cover every priced participating item of the tender. |
report | boolean | No | true returns the fill report as JSON, with a download link for the file, instead of the file itself. |
{
"tender_id": 48213,
"template_base64": "UEsDBBQABgAIAAAAIQAk3kP1...",
"filename": "NPT0024-26-response.xlsx",
"vat_percent": 15,
"quotation_ids": [
5312,
5313
],
"report": true
}Returns: The filled workbook as a file (filename, content_type, size_bytes, base64). With report: true, an object with filename, download_url and the fill report.
- Only rows of items priced above zero and marked as participating are filled. Rows without a saved price stay as they are and come back hidden rather than deleted, so NUPCO's upload still reads them. A row the fill cannot decide (a material code shared by several rows, a missing mandatory field, no product selected) is reported with its row number, never guessed, and stays visible.
- The
reportcarriestemplate_rows,filled_rows,hidden_rows,issues,missing_mandatory(the row, the material code and the missing fields),not_applicable(service lines: the cells that describe goods, such as manufacturer and catalog number, which are empty and not missing),unmatched_priced_codes, and the countspriced_items_not_participating,zero_priced_itemsandpriced_items_outside_selection. Withquotation_idsit addsquotation_scopeand per-row issues:quotation_price_mismatch(a selected quotation states another price than the saved bid price, and the bid price is what the file gets),quotation_not_approved(a quotation still in draft) andmultiple_quotations(several selected quotations cover one item, and the newest approved one is cited). - A 400 comes back with
wrong_template_tenderwhen the file is for another tender,invalid_file_typewhen it is not an xlsx,missing_columns(itsdetailnames them),no_participating_itemswhen items are priced but none is marked,no_bid_pricingwhen nothing is priced above zero, andquotation_scope_emptywhen the selected quotations quote none of the tender's items. A tender your organization cannot see answers 404tender_not_found.
tenders_nupco_attachments_fill
Builds the NUPCO attachments packet for one tender from scratch: the tab-delimited text file that NUPCO's SRM upload takes, whose columns name PDF files, plus every PDF it names under exactly that name, so the extracted files are the upload set. It is built in the background; the tool waits for it and returns the file, or the job when it takes longer.
Read-only · scope bids.read · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID, from tenders_list. |
rfx_response_number | string | Yes | The SRM RFx response number shown on the bid in NUPCO's e-portal, such as 5000061479. It is printed on every line of the file, so it must be the real one: ask the bid's owner and never invent it. Plain text, 1 to 40 characters. |
quotation_ids | integer[] | No | The quotation IDs, from `tender_quotations_list`, normally the approved run. The file covers only the items those quotations quote and cites exactly them. Every ID must be one of your organization's quotations on this tender, or the call answers 400 quotation_tender_mismatch. From 1 to 5,000 IDs. Omit it to cover every priced participating item of the tender. |
report | boolean | No | true returns the job with its report, which lists the missing files per line, the list of files and the part links, instead of delivering the file. |
wait_seconds | integer | No | Seconds the tool waits for the packet before it answers with the job: 40 by default, 120 at most. A small packet usually finishes inside the default. |
{
"tender_id": 48213,
"rfx_response_number": "5000061479",
"quotation_ids": [
5312,
5313
],
"wait_seconds": 60
}Returns: When the packet finishes within wait_seconds as one ZIP under 4 MB, the file (filename, content_type, size_bytes, base64) comes back with its report. Otherwise the job comes back: job_id, status, progress, parts (each with a download_url valid for an hour once the job has succeeded), report and next_step.
- A large packet comes in several ZIP parts (about 47 MB each): fetch each part with `generation_jobs_part` and extract all of them into one folder. A packet still being built answers with its
job_idand progress and keeps building in the background; follow it with `generation_jobs_get`, or find it in the Generated files card on the quotations page. - One packet builds per tender at a time. While one is being built, the tool answers with that job and
already_running: true; it may have been started for another selection or another RFx number. Cancel it with `generation_jobs_cancel` to start over. - Each line of the file is an item priced above zero and marked as participating, with Change Option C. For every item the packet cites the item's own quotation PDF, a cover letter (the quotation line's one-page product summary followed by the product's stored catalog; without a stored catalog the cover ships alone and the report flags
catalog_pages_missing), and its MDMA or SFDA document, which is the stored certificate or a compliance letter generated when the product is under registration or has no certificate, cited in both the SFDA Certificates and MDMA Annex columns. Every PDF is nameditem-<serial>-<slot>.pdf(the slots arequote,catalog,sfdaandmdma) and stays under 40 characters. - The
reportlists, per line, what is missing inmissing_files, the slots a service line does not carry (no catalog, SFDA certificate or MDMA annex: not missing) innot_applicable_files, and every file infiles, with the same exclusion counts and quotation issues as the response file. The quick checks fail before any job starts:invalid_rfx_response_number,no_bid_pricing,no_participating_itemsandquotation_scope_empty, and a tender your organization cannot see answers 404tender_not_found.
tenders_cover_letter_pdf
Generates the Arabic cover letter a supplier submits with its bid, as a PDF. It addresses the buying body, states the offer with its VAT breakdown and carries the regulatory attestation. The item count and the amounts are read from your saved bid, never typed in.
Read-only · scope bids.read · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID, from tenders_list. |
letter_number | string | No | Your organization's own outgoing-letter reference, such as DPC-1909, printed on the letter-number line. Omit it to leave the line off. Never invent one: the platform keeps no register of outgoing letters, so an invented number can collide with a real one. Up to 60 characters. |
recipient | string | No | The addressee line, in Arabic. When omitted, the buying body the tender names is addressed. Up to 300 characters. |
recipient_department | string | No | The second addressee line, in Arabic, such as the contracts and procurement department. Up to 300 characters. |
notes | string | No | An extra Arabic paragraph printed before the closing, up to 1,000 characters. |
quotation_ids | integer[] | No | The quotation IDs, from `tender_quotations_list`, normally the approved run. The file covers only the items those quotations quote and cites exactly them. Every ID must be one of your organization's quotations on this tender, or the call answers 400 quotation_tender_mismatch. From 1 to 5,000 IDs. Omit it to cover every priced participating item of the tender. |
report | boolean | No | true returns the item count, the offer, the VAT and the total as JSON, with a download link for the file, instead of the PDF itself. |
{
"tender_id": 48213,
"letter_number": "DPC-1909",
"recipient_department": "إدارة العقود والمشتريات",
"quotation_ids": [
5312,
5313
],
"report": true
}Returns: The letter as a PDF file (filename, content_type, size_bytes, base64). With report: true, an object with filename, download_url and a report holding item_count, subtotal, vat, total, vat_review, quotation_scope and quotation_price_review.
- The letter is in Arabic only and does not list enclosed documents, because the platform cannot see what was uploaded to the portal. It works for any tender, not only NUPCO's.
- The letter states the items priced above zero and marked as participating (within
quotation_idswhen sent). VAT is summed per line at the rate of the item's selected product: 15% when the product sets none or no product is selected, and 0% for a product the catalog zero-rates, exactly as the NUPCO response file computes it, so the two never contradict. The stated total includes VAT and is also written out in words, over a table that separates the offer from the VAT. - Check
vat_reviewbefore sending: it lists the lines whose VAT rate contradicts the product's medical flag, in either direction (a non-medical product that is zero-rated, which understates the bid, or a medical product at the standard rate), with the VAT each would move. The letter prints the same rate as the response file, so fix the product's VAT in the catalog first.quotation_price_reviewlists selected quotations that state another unit price than the saved bid price the letter totals. - A 400 comes back with
no_participating_itemswhen items are priced but none is marked,no_bid_pricingwhen nothing is priced above zero, andquotation_scope_emptywhen the selected quotations quote none of the tender's items. A tender your organization cannot see answers 404tender_not_found.
Background jobs
Two kinds of file are too big for one request, so they are built in the background as jobs: the NUPCO attachments packet (nupco_attachments, started by `tenders_nupco_attachments_fill`) and the quotations bundle (quotation_packages, started by `quotation_packages_bundle`). A job is queued, running, succeeded, failed or cancelled. Jobs belong to the organization, so you see the ones your colleagues started, and a finished job is kept for 24 hours. One packet builds per tender and one bundle per member at a time. The finished file is one ZIP part or more: extract all the parts into one folder.
generation_jobs_list
Lists your organization's background jobs from the last 24 hours, newest first, whoever started them, each with its status and progress. Use it to find a job a colleague started, or one you started and lost the ID of.
Read-only · scope bids.read or quotations.read · roles: owner, admin, sales, purchasing, viewer · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | No | Only this tender's jobs. |
kind | string | No | Only jobs of one kind: nupco_attachments for attachments packets, or quotation_packages for quotations bundles. |
Returns: jobs: a list, each with job_id, kind, status, progress (done, total, unit), parts (index, name and size), expires_at after success, error when it failed and a next_step hint. There is no report and there are no download links: read a single job with generation_jobs_get.
- At most 30 jobs come back. A key sees the kinds its scopes cover:
bids.readfor attachments packets andquotations.readfor quotations bundles, and asking for a kind your scopes do not cover answers 403.
generation_jobs_get
Reads one job: its status and progress, and once it has succeeded its report and its ZIP parts. It can wait up to 120 seconds for the job to finish. The parts of a finished job are delivered with generation_jobs_part.
Read-only · scope bids.read or quotations.read · roles: owner, admin, sales, purchasing, viewer · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The job's ID (a UUID): the job_id in the reply of the tool that started it, or in generation_jobs_list. |
wait_seconds | integer | No | Waits up to this many seconds for the job to finish, from 0 to 120. The default is 0: answer at once. |
Returns: The job: job_id, kind, status, progress, parts (each with index, filename, size_bytes and a download_url valid for an hour), waiting when nothing has moved it for a while, report and expires_at after success, error with its code and detail on failure, and next_step. On WhatsApp the links are left out; use generation_jobs_part to send a part as a document.
- Reading a job also moves its work along: when nothing is working on it, the read starts its next stretch of work. A job that nothing has moved for 30 minutes fails with the code
stalled. - An attachments packet's report is the one described under its tool. A quotations bundle's report names every quotation included with the part it is in, and every quotation that produced nothing by its number with a reason:
not_found,render_failedortoo_large. - An unknown job, or one that belongs to another organization, answers 404, and a key without the scope of the job's kind answers 403:
bids.readfor an attachments packet andquotations.readfor a quotations bundle.
generation_jobs_part
Delivers one ZIP part of a finished job, by the part number generation_jobs_get lists. On WhatsApp the part reaches the person as a document; elsewhere it comes back as base64 when under 4 MB, otherwise as a download link valid for an hour.
Read-only · scope bids.read or quotations.read · roles: owner, admin, sales, purchasing, viewer · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The job's ID (a UUID). |
part | integer | No | The part number, starting at 1; defaults to 1. A part that does not exist answers 404. |
Returns: The part as a file (filename, content_type, size_bytes, base64) when it is under 4 MB. Otherwise filename, size_bytes and a download_url valid for an hour, with a note that the file is too large to embed.
- One part is delivered per call, so a packet in three parts takes three calls, and all the parts are extracted into one folder.
- A job that has not finished answers 409
job_not_finished: its parts exist only once it has succeeded. The scope of the job's kind is needed,bids.readfor an attachments packet andquotations.readfor a quotations bundle, and any other answers 403.
generation_jobs_cancel
Stops a queued or running job. The parts built so far are deleted, and the job stays listed as cancelled for 24 hours. Use it before starting a new attachments packet for a tender whose packet is still being built, or a new quotations bundle while your last one is still building.
Deletes or overwrites data · scope bids.read or quotations.read · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The job's ID (a UUID). |
Returns: The job after cancellation, with the status cancelled.
- A job that is no longer queued or running answers 409
job_not_active, with the job. The scope of the job's kind is needed:bids.readfor an attachments packet andquotations.readfor a quotations bundle. - Work in progress stops at its next save, and the parts already built are deleted and cannot be recovered.