@commandable/mcp 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{DRfk9W3W.js → B6ti3873.js} +1 -1
  3. package/.output/public/_nuxt/{VvnbcAzZ.js → BZ8athzM.js} +1 -1
  4. package/.output/public/_nuxt/{DSWYWRXT.js → BuJvZwTh.js} +9 -9
  5. package/.output/public/_nuxt/{D9wFDhac.js → CiSdBjBt.js} +1 -1
  6. package/.output/public/_nuxt/{BD6mASiY.js → D_Ecm3JY.js} +1 -1
  7. package/.output/public/_nuxt/{CjAs3eBq.js → Vt66r6sN.js} +1 -1
  8. package/.output/public/_nuxt/{BUmYUDQu.js → bOjQRRUc.js} +3 -3
  9. package/.output/public/_nuxt/builds/latest.json +1 -1
  10. package/.output/public/_nuxt/builds/meta/6152dcdc-6d3b-4e0a-8c56-4584c72c8765.json +1 -0
  11. package/.output/server/chunks/build/{_id_-DA3Q8jun.mjs → _id_-BIPuj6eI.mjs} +4 -4
  12. package/.output/server/chunks/build/{_id_-DA3Q8jun.mjs.map → _id_-BIPuj6eI.mjs.map} +1 -1
  13. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  14. package/.output/server/chunks/build/{error-404-D1k2kWid.mjs → error-404-BHlLSgAJ.mjs} +1 -1
  15. package/.output/server/chunks/build/error-404-BHlLSgAJ.mjs.map +1 -0
  16. package/.output/server/chunks/build/{error-500-D2K2rAfl.mjs → error-500-D4rqaBv3.mjs} +1 -1
  17. package/.output/server/chunks/build/error-500-D4rqaBv3.mjs.map +1 -0
  18. package/.output/server/chunks/build/{fetch-aDh21opM.mjs → fetch-DJGu_p4i.mjs} +1 -1
  19. package/.output/server/chunks/build/fetch-DJGu_p4i.mjs.map +1 -0
  20. package/.output/server/chunks/build/{index-ycGPozML.mjs → index-DUjkeeyt.mjs} +1 -1
  21. package/.output/server/chunks/build/index-DUjkeeyt.mjs.map +1 -0
  22. package/.output/server/chunks/build/{index-F5lAFSQk.mjs → index-DvzWme19.mjs} +4 -4
  23. package/.output/server/chunks/build/index-DvzWme19.mjs.map +1 -0
  24. package/.output/server/chunks/build/server.mjs +5 -5
  25. package/.output/server/chunks/nitro/nitro.mjs +3008 -213
  26. package/.output/server/package.json +1 -1
  27. package/package.json +2 -2
  28. package/.output/public/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json +0 -1
  29. package/.output/server/chunks/build/error-404-D1k2kWid.mjs.map +0 -1
  30. package/.output/server/chunks/build/error-500-D2K2rAfl.mjs.map +0 -1
  31. package/.output/server/chunks/build/fetch-aDh21opM.mjs.map +0 -1
  32. package/.output/server/chunks/build/index-F5lAFSQk.mjs.map +0 -1
  33. package/.output/server/chunks/build/index-ycGPozML.mjs.map +0 -1
@@ -4431,7 +4431,7 @@ function _expandFromEnv(value) {
4431
4431
  const _inlineRuntimeConfig = {
4432
4432
  "app": {
4433
4433
  "baseURL": "/",
4434
- "buildId": "886deef4-f3b5-464c-b4e2-11735eb5272e",
4434
+ "buildId": "6152dcdc-6d3b-4e0a-8c56-4584c72c8765",
4435
4435
  "buildAssetsDir": "/_nuxt/",
4436
4436
  "cdnURL": ""
4437
4437
  },
@@ -4900,7 +4900,7 @@ async function errorHandler(error, event) {
4900
4900
 
4901
4901
  const script = "\"use strict\";(()=>{const t=window,e=document.documentElement,c=[\"dark\",\"light\"],n=getStorageValue(\"localStorage\",\"nuxt-color-mode\")||\"system\";let i=n===\"system\"?u():n;const r=e.getAttribute(\"data-color-mode-forced\");r&&(i=r),l(i),t[\"__NUXT_COLOR_MODE__\"]={preference:n,value:i,getColorScheme:u,addColorScheme:l,removeColorScheme:d};function l(o){const s=\"\"+o+\"\",a=\"\";e.classList?e.classList.add(s):e.className+=\" \"+s,a&&e.setAttribute(\"data-\"+a,o)}function d(o){const s=\"\"+o+\"\",a=\"\";e.classList?e.classList.remove(s):e.className=e.className.replace(new RegExp(s,\"g\"),\"\"),a&&e.removeAttribute(\"data-\"+a)}function f(o){return t.matchMedia(\"(prefers-color-scheme\"+o+\")\")}function u(){if(t.matchMedia&&f(\"\").media!==\"not all\"){for(const o of c)if(f(\":\"+o).matches)return o}return\"light\"}})();function getStorageValue(t,e){switch(t){case\"localStorage\":return window.localStorage.getItem(e);case\"sessionStorage\":return window.sessionStorage.getItem(e);case\"cookie\":return getCookie(e);default:return null}}function getCookie(t){const c=(\"; \"+window.document.cookie).split(\"; \"+t+\"=\");if(c.length===2)return c.pop()?.split(\";\").shift()}";
4902
4902
 
4903
- const _z25E5bI6lxolM40nRjOSnV7rTGIEhhG_oLaxlR5RK0I = (function(nitro) {
4903
+ const _zUuxVGgAOQhH7E5atfn9Ptf95wF4esqexnSnzyTPmK8 = (function(nitro) {
4904
4904
  nitro.hooks.hook("render:html", (htmlContext) => {
4905
4905
  htmlContext.head.push(`<script>${script}<\/script>`);
4906
4906
  });
@@ -9058,12 +9058,20 @@ const GENERATED_INTEGRATIONS = {
9058
9058
  },
9059
9059
  {
9060
9060
  "name": "read_email",
9061
- "description": "Read an email by message ID and return a flat, decoded result with subject, from, to, cc, date, snippet, body text, labelIds, and threadId. Use this for reading email content -- it handles base64 decoding and header extraction automatically. For raw API access or advanced format options, use get_message instead.",
9061
+ "description": "Read an email by message ID and return a flat, decoded result with subject, from, to, cc, date, snippet, body text, labelIds, threadId, and attachment metadata. Use this for reading email content -- it handles base64 decoding and header extraction automatically. To extract an attachment's readable file content, use read_attachment_content with an attachmentId or filename from this result. For raw API access or advanced format options, use get_message instead.",
9062
9062
  "inputSchema": "schemas/read_email.json",
9063
9063
  "handler": "handlers/read_email.js",
9064
9064
  "scope": "read",
9065
9065
  "toolset": "email"
9066
9066
  },
9067
+ {
9068
+ "name": "read_attachment_content",
9069
+ "description": "Read and extract the content of a Gmail message attachment into agent-friendly text. Use read_email first to discover attachmentId, filename, mimeType, and size. Supports PDFs, Office files, CSV, text, HTML, and other formats supported by Commandable file processing. Provide attachmentId when possible; filename can be used as a fallback.",
9070
+ "inputSchema": "schemas/read_attachment_content.json",
9071
+ "handler": "handlers/read_attachment_content.js",
9072
+ "scope": "read",
9073
+ "toolset": "email"
9074
+ },
9067
9075
  {
9068
9076
  "name": "get_message",
9069
9077
  "description": "Get the raw Gmail message resource by message ID. Returns the full nested payload including base64url-encoded body parts and all headers. For most use cases, prefer read_email which decodes the response into a flat readable format. Use format='minimal' for lightweight ID+threadId+labelIds only, format='metadata' with metadataHeaders for specific headers only.",
@@ -9242,7 +9250,7 @@ const GENERATED_INTEGRATIONS = {
9242
9250
  }
9243
9251
  ]
9244
9252
  },
9245
- "usageGuide": "## Gmail search query syntax\n\nGmail's `q` parameter supports a powerful search language. Key operators:\n\n- `is:unread` / `is:read` \u2014 filter by read status\n- `is:starred`, `is:important` \u2014 filter by markers\n- `from:user@example.com` \u2014 sender filter\n- `to:user@example.com`, `cc:user@example.com` \u2014 recipient filters\n- `subject:keyword` \u2014 subject line search\n- `has:attachment` \u2014 messages with attachments\n- `filename:report.pdf` \u2014 specific attachment filename\n- `label:INBOX` \u2014 filter by label (use label name or ID)\n- `after:2024/01/01`, `before:2024/12/31` \u2014 date range (YYYY/MM/DD)\n- `newer_than:7d`, `older_than:1y` \u2014 relative time (d=days, m=months, y=years)\n- `in:sent`, `in:drafts`, `in:trash`, `in:spam` \u2014 folder filters\n- `larger:5M`, `smaller:1M` \u2014 size filters\n\nCombine operators with spaces (implicit AND): `from:alice is:unread has:attachment`\n\n## Recommended workflows\n\n**Reading emails:**\n1. Use `list_messages` with a `q` query to find relevant message IDs\n2. Use `read_email` on each ID to get decoded subject, from, to, date, and body text\n3. For raw access or advanced format options, use `get_message` with `format='full'`\n\n**Searching for threads:**\n1. Use `list_threads` with `q` to find conversation threads\n2. Use `get_thread` to retrieve all messages in a conversation at once\n\n**Sending email:**\n- Use `send_email` for the vast majority of cases -- it accepts plain `to`, `subject`, `body` fields\n- Use `create_draft_email` + `send_draft` when you want to create a draft for review before sending\n\n**Replying to an email:**\n1. Get the original message with `read_email` to obtain its `threadId` and `id`\n2. Call `send_email` with `replyToMessageId` = original message `id` and `threadId` = original `threadId`\n3. The reply will appear in the same conversation thread\n\n## Label IDs\n\nSystem label IDs (always uppercase): `INBOX`, `UNREAD`, `STARRED`, `IMPORTANT`, `SENT`, `DRAFT`, `SPAM`, `TRASH`, `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS`\n\nUser-created labels have auto-generated IDs. Use `list_labels` to discover them.\n\n## Archiving and organizing\n\n- Archive a message: `modify_message` with `removeLabelIds=['INBOX']`\n- Mark as read: `modify_message` with `removeLabelIds=['UNREAD']`\n- Star a message: `modify_message` with `addLabelIds=['STARRED']`\n- Apply a label: `modify_message` with `addLabelIds=['<labelId>']`\n- Use `modify_thread` to apply the same operation to all messages in a thread at once\n",
9253
+ "usageGuide": "## Gmail search query syntax\n\nGmail's `q` parameter supports a powerful search language. Key operators:\n\n- `is:unread` / `is:read` \u2014 filter by read status\n- `is:starred`, `is:important` \u2014 filter by markers\n- `from:user@example.com` \u2014 sender filter\n- `to:user@example.com`, `cc:user@example.com` \u2014 recipient filters\n- `subject:keyword` \u2014 subject line search\n- `has:attachment` \u2014 messages with attachments\n- `filename:report.pdf` \u2014 specific attachment filename\n- `label:INBOX` \u2014 filter by label (use label name or ID)\n- `after:2024/01/01`, `before:2024/12/31` \u2014 date range (YYYY/MM/DD)\n- `newer_than:7d`, `older_than:1y` \u2014 relative time (d=days, m=months, y=years)\n- `in:sent`, `in:drafts`, `in:trash`, `in:spam` \u2014 folder filters\n- `larger:5M`, `smaller:1M` \u2014 size filters\n\nCombine operators with spaces (implicit AND): `from:alice is:unread has:attachment`\n\n## Recommended workflows\n\n**Reading emails:**\n1. Use `list_messages` with a `q` query to find relevant message IDs\n2. Use `read_email` on each ID to get decoded subject, from, to, date, body text, and attachment metadata\n3. For raw access or advanced format options, use `get_message` with `format='full'`\n\n**Reading attachments:**\n1. Use `list_messages` with `has:attachment` or `filename:report.pdf` to find messages with attachments\n2. Use `read_email` to inspect the `attachments` array and find the attachment ID, filename, MIME type, and size\n3. Use `read_attachment_content` with the message ID and either `attachmentId` or `filename` to extract readable file content\n\n**Searching for threads:**\n1. Use `list_threads` with `q` to find conversation threads\n2. Use `get_thread` to retrieve all messages in a conversation at once\n\n**Sending email:**\n- Use `send_email` for the vast majority of cases -- it accepts plain `to`, `subject`, `body` fields\n- Use `create_draft_email` + `send_draft` when you want to create a draft for review before sending\n\n**Replying to an email:**\n1. Get the original message with `read_email` to obtain its `threadId` and `id`\n2. Call `send_email` with `replyToMessageId` = original message `id` and `threadId` = original `threadId`\n3. The reply will appear in the same conversation thread\n\n## Label IDs\n\nSystem label IDs (always uppercase): `INBOX`, `UNREAD`, `STARRED`, `IMPORTANT`, `SENT`, `DRAFT`, `SPAM`, `TRASH`, `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS`\n\nUser-created labels have auto-generated IDs. Use `list_labels` to discover them.\n\n## Archiving and organizing\n\n- Archive a message: `modify_message` with `removeLabelIds=['INBOX']`\n- Mark as read: `modify_message` with `removeLabelIds=['UNREAD']`\n- Star a message: `modify_message` with `addLabelIds=['STARRED']`\n- Apply a label: `modify_message` with `addLabelIds=['<labelId>']`\n- Use `modify_thread` to apply the same operation to all messages in a thread at once\n",
9246
9254
  "variants": {
9247
9255
  "variants": {
9248
9256
  "service_account": {
@@ -9382,7 +9390,7 @@ const GENERATED_INTEGRATIONS = {
9382
9390
  },
9383
9391
  {
9384
9392
  "name": "read_email",
9385
- "description": "Read an email by message ID and return a flat, decoded result with subject, from, to, cc, date, snippet, body text, labelIds, and threadId. Use this for reading email content -- it handles base64 decoding and header extraction automatically. For raw API access or advanced format options, use get_message instead.",
9393
+ "description": "Read an email by message ID and return a flat, decoded result with subject, from, to, cc, date, snippet, body text, labelIds, threadId, and attachment metadata. Use this for reading email content -- it handles base64 decoding and header extraction automatically. To extract an attachment's readable file content, use read_attachment_content with an attachmentId or filename from this result. For raw API access or advanced format options, use get_message instead.",
9386
9394
  "inputSchema": {
9387
9395
  "$schema": "http://json-schema.org/draft-07/schema#",
9388
9396
  "type": "object",
@@ -9401,7 +9409,62 @@ const GENERATED_INTEGRATIONS = {
9401
9409
  },
9402
9410
  "additionalProperties": false
9403
9411
  },
9404
- "handlerCode": "async (input) => {\n const userId = encodeURIComponent(input.userId || 'me')\n const messageId = encodeURIComponent(input.messageId)\n const res = await integration.fetch(`/users/${userId}/messages/${messageId}?format=full`)\n const msg = await res.json()\n\n const getHeader = (name) => {\n const h = (msg.payload?.headers || []).find(h => h.name.toLowerCase() === name.toLowerCase())\n return h?.value || ''\n }\n\n const decodeBase64url = (data) => {\n if (!data) return ''\n try {\n return decodeURIComponent(escape(atob(data.replace(/-/g, '+').replace(/_/g, '/'))))\n }\n catch {\n return ''\n }\n }\n\n // Recursively extract text body, preferring text/plain over text/html\n const extractBody = (part) => {\n if (!part) return ''\n if (part.mimeType === 'text/plain' && part.body?.data)\n return decodeBase64url(part.body.data)\n if (part.parts) {\n // Depth-first: try text/plain first across all parts\n for (const p of part.parts) {\n if (p.mimeType === 'text/plain' && p.body?.data)\n return decodeBase64url(p.body.data)\n }\n // Recurse into nested multipart\n for (const p of part.parts) {\n const text = extractBody(p)\n if (text) return text\n }\n }\n if (part.mimeType === 'text/html' && part.body?.data)\n return decodeBase64url(part.body.data)\n return ''\n }\n\n return {\n id: msg.id,\n threadId: msg.threadId,\n labelIds: msg.labelIds || [],\n subject: getHeader('Subject'),\n from: getHeader('From'),\n to: getHeader('To'),\n cc: getHeader('Cc'),\n date: getHeader('Date'),\n snippet: msg.snippet || '',\n body: extractBody(msg.payload),\n }\n}",
9412
+ "handlerCode": "async (input) => {\n const userId = encodeURIComponent(input.userId || 'me')\n const messageId = encodeURIComponent(input.messageId)\n const res = await integration.fetch(`/users/${userId}/messages/${messageId}?format=full`)\n const msg = await res.json()\n\n const getHeader = (name) => {\n const h = (msg.payload?.headers || []).find(h => h.name.toLowerCase() === name.toLowerCase())\n return h?.value || ''\n }\n\n const decodeBase64url = (data) => {\n if (!data) return ''\n try {\n return decodeURIComponent(escape(atob(data.replace(/-/g, '+').replace(/_/g, '/'))))\n }\n catch {\n return ''\n }\n }\n\n // Recursively extract text body, preferring text/plain over text/html\n const extractBody = (part) => {\n if (!part) return ''\n if (part.mimeType === 'text/plain' && part.body?.data)\n return decodeBase64url(part.body.data)\n if (part.parts) {\n // Depth-first: try text/plain first across all parts\n for (const p of part.parts) {\n if (p.mimeType === 'text/plain' && p.body?.data)\n return decodeBase64url(p.body.data)\n }\n // Recurse into nested multipart\n for (const p of part.parts) {\n const text = extractBody(p)\n if (text) return text\n }\n }\n if (part.mimeType === 'text/html' && part.body?.data)\n return decodeBase64url(part.body.data)\n return ''\n }\n\n const collectAttachments = (part, out = []) => {\n if (!part) return out\n if (part.body?.attachmentId) {\n out.push({\n attachmentId: part.body.attachmentId,\n filename: part.filename || '',\n mimeType: part.mimeType || '',\n size: part.body?.size || 0,\n partId: part.partId || '',\n })\n }\n if (Array.isArray(part.parts)) {\n for (const child of part.parts)\n collectAttachments(child, out)\n }\n return out\n }\n\n return {\n id: msg.id,\n threadId: msg.threadId,\n labelIds: msg.labelIds || [],\n subject: getHeader('Subject'),\n from: getHeader('From'),\n to: getHeader('To'),\n cc: getHeader('Cc'),\n date: getHeader('Date'),\n snippet: msg.snippet || '',\n body: extractBody(msg.payload),\n attachments: collectAttachments(msg.payload),\n }\n}",
9413
+ "scope": "read",
9414
+ "toolset": "email"
9415
+ },
9416
+ {
9417
+ "name": "read_attachment_content",
9418
+ "description": "Read and extract the content of a Gmail message attachment into agent-friendly text. Use read_email first to discover attachmentId, filename, mimeType, and size. Supports PDFs, Office files, CSV, text, HTML, and other formats supported by Commandable file processing. Provide attachmentId when possible; filename can be used as a fallback.",
9419
+ "inputSchema": {
9420
+ "$schema": "http://json-schema.org/draft-07/schema#",
9421
+ "type": "object",
9422
+ "required": [
9423
+ "messageId"
9424
+ ],
9425
+ "anyOf": [
9426
+ {
9427
+ "required": [
9428
+ "attachmentId"
9429
+ ]
9430
+ },
9431
+ {
9432
+ "required": [
9433
+ "filename"
9434
+ ]
9435
+ }
9436
+ ],
9437
+ "properties": {
9438
+ "userId": {
9439
+ "type": "string",
9440
+ "description": "User email or 'me'. Defaults to 'me'."
9441
+ },
9442
+ "messageId": {
9443
+ "type": "string",
9444
+ "description": "Gmail message ID. Obtain from list_messages or read_email."
9445
+ },
9446
+ "attachmentId": {
9447
+ "type": "string",
9448
+ "description": "Gmail attachment ID from read_email's attachments array. Preferred when available."
9449
+ },
9450
+ "filename": {
9451
+ "type": "string",
9452
+ "description": "Attachment filename from read_email's attachments array. Used to resolve the attachment when attachmentId is not provided."
9453
+ },
9454
+ "mimeType": {
9455
+ "type": "string",
9456
+ "description": "Optional MIME type override. Defaults to the MIME type from the matching message part."
9457
+ },
9458
+ "previewPages": {
9459
+ "type": "integer",
9460
+ "minimum": 1,
9461
+ "maximum": 10,
9462
+ "description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
9463
+ }
9464
+ },
9465
+ "additionalProperties": false
9466
+ },
9467
+ "handlerCode": "async (input) => {\n const userId = encodeURIComponent(input.userId || 'me')\n const messageId = encodeURIComponent(input.messageId)\n\n const collectAttachments = (part, out = []) => {\n if (!part) return out\n if (part.body?.attachmentId) {\n out.push({\n attachmentId: part.body.attachmentId,\n filename: part.filename || '',\n mimeType: part.mimeType || '',\n size: part.body?.size || 0,\n partId: part.partId || '',\n })\n }\n if (Array.isArray(part.parts)) {\n for (const child of part.parts)\n collectAttachments(child, out)\n }\n return out\n }\n\n const toBase64 = (value) => {\n const raw = String(value || '').replace(/-/g, '+').replace(/_/g, '/')\n return raw.padEnd(Math.ceil(raw.length / 4) * 4, '=')\n }\n\n const messageRes = await integration.fetch(`/users/${userId}/messages/${messageId}?format=full`)\n if (!messageRes.ok)\n throw new Error(`Failed to fetch Gmail message (${messageRes.status}).`)\n const message = await messageRes.json()\n const attachments = collectAttachments(message.payload)\n\n const requestedAttachmentId = String(input.attachmentId || '')\n const requestedFilename = String(input.filename || '').toLowerCase()\n const found = requestedAttachmentId\n ? attachments.find(item => item.attachmentId === requestedAttachmentId)\n : attachments.find(item => item.filename.toLowerCase() === requestedFilename)\n const match = found || (requestedAttachmentId\n ? {\n attachmentId: requestedAttachmentId,\n filename: input.filename || '',\n mimeType: input.mimeType || '',\n size: 0,\n partId: '',\n }\n : null)\n\n if (!match) {\n return {\n messageId: input.messageId,\n attachmentId: input.attachmentId || null,\n filename: input.filename || null,\n content: null,\n message: 'Attachment not found on this Gmail message. Use read_email to inspect available attachments.',\n attachments,\n }\n }\n\n const attachmentId = encodeURIComponent(match.attachmentId)\n const attachmentRes = await integration.fetch(`/users/${userId}/messages/${messageId}/attachments/${attachmentId}`)\n if (!attachmentRes.ok)\n throw new Error(`Failed to fetch Gmail attachment (${attachmentRes.status}).`)\n const attachment = await attachmentRes.json()\n\n if (!attachment?.data) {\n return {\n messageId: input.messageId,\n attachmentId: match.attachmentId,\n filename: match.filename,\n mimeType: input.mimeType || match.mimeType || null,\n size: match.size,\n content: null,\n message: 'Gmail attachment response did not include file data.',\n }\n }\n\n const mimeType = input.mimeType || match.mimeType || 'application/octet-stream'\n const extracted = await utils.extractFileContent({\n auth: false,\n source: `data:${mimeType};base64,${toBase64(attachment.data)}`,\n previewPages: input.previewPages || 0,\n })\n\n return {\n messageId: input.messageId,\n attachmentId: match.attachmentId,\n filename: match.filename,\n mimeType,\n size: match.size || attachment.size || 0,\n partId: match.partId,\n ...extracted,\n }\n}",
9405
9468
  "scope": "read",
9406
9469
  "toolset": "email"
9407
9470
  },
@@ -9769,7 +9832,7 @@ const GENERATED_INTEGRATIONS = {
9769
9832
  ],
9770
9833
  "additionalProperties": false
9771
9834
  },
9772
- "handlerCode": "async (input) => {\n const userId = encodeURIComponent(input.userId || 'me')\n const body = {}\n if (input.draftId)\n body.id = input.draftId\n if (input.raw) {\n body.message = { raw: input.raw }\n if (input.threadId)\n body.message.threadId = input.threadId\n if (Array.isArray(input.labelIds))\n body.message.labelIds = input.labelIds\n }\n const res = await integration.fetch(`/users/${userId}/drafts/send`, { method: 'POST', body })\n return await res.json()\n}",
9835
+ "handlerCode": "async (input) => {\n const userId = encodeURIComponent(input.userId || 'me')\n if (input.raw && !input.draftId) {\n const message = { raw: input.raw }\n if (input.threadId)\n message.threadId = input.threadId\n if (Array.isArray(input.labelIds))\n message.labelIds = input.labelIds\n const res = await integration.fetch(`/users/${userId}/messages/send`, { method: 'POST', body: message })\n return await res.json()\n }\n\n const body = {}\n if (input.draftId)\n body.id = input.draftId\n if (input.raw) {\n body.message = { raw: input.raw }\n if (input.threadId)\n body.message.threadId = input.threadId\n if (Array.isArray(input.labelIds))\n body.message.labelIds = input.labelIds\n }\n const res = await integration.fetch(`/users/${userId}/drafts/send`, { method: 'POST', body })\n return await res.json()\n}",
9773
9836
  "scope": "write",
9774
9837
  "toolset": "email"
9775
9838
  },
@@ -10666,7 +10729,8 @@ const GENERATED_INTEGRATIONS = {
10666
10729
  },
10667
10730
  "preprocess": "google_service_account",
10668
10731
  "healthCheck": {
10669
- "notViable": true
10732
+ "path": "/about?fields=user",
10733
+ "description": "Validates that the service account can mint a Google access token and call the Drive API."
10670
10734
  }
10671
10735
  },
10672
10736
  "oauth_token": {
@@ -10691,7 +10755,8 @@ const GENERATED_INTEGRATIONS = {
10691
10755
  }
10692
10756
  },
10693
10757
  "healthCheck": {
10694
- "notViable": true
10758
+ "path": "/about?fields=user",
10759
+ "description": "Validates that the OAuth token can call the Drive API."
10695
10760
  }
10696
10761
  }
10697
10762
  },
@@ -15980,7 +16045,8 @@ const GENERATED_INTEGRATIONS = {
15980
16045
  }
15981
16046
  },
15982
16047
  "healthCheck": {
15983
- "notViable": true
16048
+ "path": "/sites?search=*&$top=1",
16049
+ "description": "Validates that app credentials can obtain a Microsoft Graph token and read SharePoint sites."
15984
16050
  }
15985
16051
  }
15986
16052
  },
@@ -16474,7 +16540,8 @@ const GENERATED_INTEGRATIONS = {
16474
16540
  }
16475
16541
  },
16476
16542
  "healthCheck": {
16477
- "notViable": true
16543
+ "path": "/sites?search=*&$top=1",
16544
+ "description": "Validates that app credentials can obtain a Microsoft Graph token and read SharePoint sites."
16478
16545
  }
16479
16546
  }
16480
16547
  },
@@ -17793,7 +17860,7 @@ const GENERATED_INTEGRATIONS = {
17793
17860
  "name": "Trello",
17794
17861
  "version": "0.1.0",
17795
17862
  "baseUrl": "https://api.trello.com/1",
17796
- "variantLabel": "Single board",
17863
+ "variantLabel": "Single Board",
17797
17864
  "variantConfig": [
17798
17865
  {
17799
17866
  "key": "board",
@@ -18641,58 +18708,2752 @@ const GENERATED_INTEGRATIONS = {
18641
18708
  }
18642
18709
  ],
18643
18710
  "variantOwnerType": "trello"
18644
- }
18645
- };
18646
-
18647
- function humanizeName(name) {
18648
- return name.replace(/_/g, " ").split(/\s+/).filter(Boolean).map((w) => w.length ? `${w[0].toUpperCase()}${w.slice(1).toLowerCase()}` : w).join(" ");
18649
- }
18650
- function getIntegration(type) {
18651
- var _a;
18652
- return (_a = GENERATED_INTEGRATIONS[type]) != null ? _a : null;
18653
- }
18654
- function cloneManifest(manifest) {
18655
- return {
18656
- ...manifest,
18657
- variantConfig: manifest.variantConfig ? JSON.parse(JSON.stringify(manifest.variantConfig)) : void 0,
18658
- toolsets: manifest.toolsets ? { ...manifest.toolsets } : void 0,
18659
- tools: manifest.tools.map((tool) => ({
18660
- ...tool,
18661
- injectFromConfig: tool.injectFromConfig ? { ...tool.injectFromConfig } : void 0
18662
- }))
18663
- };
18664
- }
18665
- function cloneCredentialVariant(variant) {
18666
- var _a, _b;
18667
- return {
18668
- ...variant,
18669
- injection: {
18670
- headers: ((_a = variant.injection) == null ? void 0 : _a.headers) ? { ...variant.injection.headers } : void 0,
18671
- query: ((_b = variant.injection) == null ? void 0 : _b.query) ? { ...variant.injection.query } : void 0
18711
+ },
18712
+ "xero": {
18713
+ "manifest": {
18714
+ "name": "xero",
18715
+ "version": "0.1.0",
18716
+ "baseUrl": "https://api.xero.com",
18717
+ "toolsets": {
18718
+ "accounting": {
18719
+ "label": "Accounting",
18720
+ "description": "Work with Xero contacts, items, invoices, payments, bank transactions, journals, and attachments"
18721
+ },
18722
+ "reports": {
18723
+ "label": "Reports",
18724
+ "description": "Read Xero financial reports such as profit and loss, balance sheet, trial balance, and aged reports"
18725
+ }
18726
+ },
18727
+ "tools": [
18728
+ {
18729
+ "name": "list_connections",
18730
+ "description": "List Xero tenant connections available to the current token. Use this for public OAuth integrations to discover tenantId values; Custom Connections are single-organisation and usually do not require tenantId.",
18731
+ "inputSchema": "schemas/empty.json",
18732
+ "handler": "handlers/list_connections.js",
18733
+ "scope": "read"
18734
+ },
18735
+ {
18736
+ "name": "get_organisation",
18737
+ "description": "Get the connected Xero organisation profile and settings. For public OAuth, provide tenantId from list_connections; for Custom Connections omit tenantId.",
18738
+ "inputSchema": "schemas/tenant_optional.json",
18739
+ "handler": "handlers/get_organisation.js",
18740
+ "scope": "read"
18741
+ },
18742
+ {
18743
+ "name": "list_accounts",
18744
+ "description": "List chart of accounts with account IDs, codes, names, types, status, and tax type. Use this before creating payments, bank transactions, or journals.",
18745
+ "inputSchema": "schemas/list_accounts.json",
18746
+ "handler": "handlers/list_accounts.js",
18747
+ "scope": "read"
18748
+ },
18749
+ {
18750
+ "name": "list_tax_rates",
18751
+ "description": "List tax rates available in the connected organisation. Use this to discover valid tax type values for invoice, item, and bank transaction lines.",
18752
+ "inputSchema": "schemas/tenant_optional.json",
18753
+ "handler": "handlers/list_tax_rates.js",
18754
+ "scope": "read"
18755
+ },
18756
+ {
18757
+ "name": "list_tracking_categories",
18758
+ "description": "List tracking categories and active options. Use this before adding tracking to invoice, journal, or bank transaction lines.",
18759
+ "inputSchema": "schemas/tenant_optional.json",
18760
+ "handler": "handlers/list_tracking_categories.js",
18761
+ "scope": "read"
18762
+ },
18763
+ {
18764
+ "name": "list_contact_groups",
18765
+ "description": "List contact groups, or provide contactGroupId to fetch one group with contacts. Use this when users ask about Xero customer/supplier groupings.",
18766
+ "inputSchema": "schemas/contact_group_list.json",
18767
+ "handler": "handlers/list_contact_groups.js",
18768
+ "scope": "read",
18769
+ "toolset": "accounting"
18770
+ },
18771
+ {
18772
+ "name": "create_tracking_category",
18773
+ "description": "Create a tracking category. Use list_tracking_categories first to avoid duplicates.",
18774
+ "inputSchema": "schemas/tracking_category_write.json",
18775
+ "handler": "handlers/create_tracking_category.js",
18776
+ "scope": "write",
18777
+ "toolset": "accounting"
18778
+ },
18779
+ {
18780
+ "name": "create_tracking_options",
18781
+ "description": "Create up to 10 tracking options in a tracking category. Get trackingCategoryId from list_tracking_categories.",
18782
+ "inputSchema": "schemas/tracking_options_write.json",
18783
+ "handler": "handlers/create_tracking_options.js",
18784
+ "scope": "write",
18785
+ "toolset": "accounting"
18786
+ },
18787
+ {
18788
+ "name": "update_tracking_category",
18789
+ "description": "Rename or archive a tracking category. Get trackingCategoryId from list_tracking_categories.",
18790
+ "inputSchema": "schemas/tracking_category_write.json",
18791
+ "handler": "handlers/update_tracking_category.js",
18792
+ "scope": "write",
18793
+ "toolset": "accounting"
18794
+ },
18795
+ {
18796
+ "name": "update_tracking_options",
18797
+ "description": "Rename or archive a tracking option. Get trackingCategoryId and trackingOptionId from list_tracking_categories.",
18798
+ "inputSchema": "schemas/tracking_options_write.json",
18799
+ "handler": "handlers/update_tracking_options.js",
18800
+ "scope": "write",
18801
+ "toolset": "accounting"
18802
+ },
18803
+ {
18804
+ "name": "list_currencies",
18805
+ "description": "List currencies configured in the connected Xero organisation.",
18806
+ "inputSchema": "schemas/tenant_optional.json",
18807
+ "handler": "handlers/list_currencies.js",
18808
+ "scope": "read"
18809
+ },
18810
+ {
18811
+ "name": "list_contacts",
18812
+ "description": "List contacts with compact identity and balance fields. Filter with where/order/page when needed; use get_contact for full details before updating.",
18813
+ "inputSchema": "schemas/list_records.json",
18814
+ "handler": "handlers/list_contacts.js",
18815
+ "scope": "read",
18816
+ "toolset": "accounting"
18817
+ },
18818
+ {
18819
+ "name": "get_contact",
18820
+ "description": "Get a Xero contact by contactId. Use this before update_contact to avoid overwriting important fields.",
18821
+ "inputSchema": "schemas/id_record.json",
18822
+ "handler": "handlers/get_contact.js",
18823
+ "scope": "read",
18824
+ "toolset": "accounting"
18825
+ },
18826
+ {
18827
+ "name": "create_contact",
18828
+ "description": "Create a contact from flat fields. Use extraFields only for advanced Xero fields; returns a compact summary and Xero link when available.",
18829
+ "inputSchema": "schemas/contact_write.json",
18830
+ "handler": "handlers/create_contact.js",
18831
+ "scope": "write",
18832
+ "toolset": "accounting"
18833
+ },
18834
+ {
18835
+ "name": "update_contact",
18836
+ "description": "Update a contact by contactId. Provide only fields to change; use get_contact first when preserving existing values matters.",
18837
+ "inputSchema": "schemas/contact_write.json",
18838
+ "handler": "handlers/update_contact.js",
18839
+ "scope": "write",
18840
+ "toolset": "accounting"
18841
+ },
18842
+ {
18843
+ "name": "list_items",
18844
+ "description": "List inventory/service items with IDs, codes, names, status, and sale/purchase details. Use get_item for full details.",
18845
+ "inputSchema": "schemas/list_records.json",
18846
+ "handler": "handlers/list_items.js",
18847
+ "scope": "read",
18848
+ "toolset": "accounting"
18849
+ },
18850
+ {
18851
+ "name": "get_item",
18852
+ "description": "Get a Xero item by itemId.",
18853
+ "inputSchema": "schemas/id_record.json",
18854
+ "handler": "handlers/get_item.js",
18855
+ "scope": "read",
18856
+ "toolset": "accounting"
18857
+ },
18858
+ {
18859
+ "name": "create_item",
18860
+ "description": "Create an item from flat fields. Use extraFields only for advanced Xero fields.",
18861
+ "inputSchema": "schemas/item_write.json",
18862
+ "handler": "handlers/create_item.js",
18863
+ "scope": "write",
18864
+ "toolset": "accounting"
18865
+ },
18866
+ {
18867
+ "name": "update_item",
18868
+ "description": "Update an item by itemId. Provide only fields to change.",
18869
+ "inputSchema": "schemas/item_write.json",
18870
+ "handler": "handlers/update_item.js",
18871
+ "scope": "write",
18872
+ "toolset": "accounting"
18873
+ },
18874
+ {
18875
+ "name": "list_invoices",
18876
+ "description": "List invoices with compact totals, status, contact, dates, and invoice number. Prefer explicit filters like status, contactIds, invoiceNumbers, fromDate/toDate; use get_invoice for line items.",
18877
+ "inputSchema": "schemas/list_records.json",
18878
+ "handler": "handlers/list_invoices.js",
18879
+ "scope": "read",
18880
+ "toolset": "accounting"
18881
+ },
18882
+ {
18883
+ "name": "get_invoice",
18884
+ "description": "Get an invoice by invoiceId, including line items and payment summary.",
18885
+ "inputSchema": "schemas/id_record.json",
18886
+ "handler": "handlers/get_invoice.js",
18887
+ "scope": "read",
18888
+ "toolset": "accounting"
18889
+ },
18890
+ {
18891
+ "name": "create_invoice",
18892
+ "description": "Create an invoice or bill from flat fields. Get contactId from list_contacts, accountCode from list_accounts, taxType from list_tax_rates, and tracking from list_tracking_categories. Returns a compact summary and Xero link.",
18893
+ "inputSchema": "schemas/invoice_write.json",
18894
+ "handler": "handlers/create_invoice.js",
18895
+ "scope": "write",
18896
+ "toolset": "accounting"
18897
+ },
18898
+ {
18899
+ "name": "update_invoice",
18900
+ "description": "Update an invoice by invoiceId. Use this for draft changes or allowed status transitions; prefer get_invoice first.",
18901
+ "inputSchema": "schemas/invoice_write.json",
18902
+ "handler": "handlers/update_invoice.js",
18903
+ "scope": "write",
18904
+ "toolset": "accounting"
18905
+ },
18906
+ {
18907
+ "name": "list_credit_notes",
18908
+ "description": "List credit notes with compact status, contact, date, total, and remaining credit fields.",
18909
+ "inputSchema": "schemas/list_records.json",
18910
+ "handler": "handlers/list_credit_notes.js",
18911
+ "scope": "read",
18912
+ "toolset": "accounting"
18913
+ },
18914
+ {
18915
+ "name": "create_credit_note",
18916
+ "description": "Create a credit note from flat fields. Get contactId from list_contacts, accountCode from list_accounts, and taxType from list_tax_rates. Returns a compact summary and Xero link.",
18917
+ "inputSchema": "schemas/credit_note_write.json",
18918
+ "handler": "handlers/create_credit_note.js",
18919
+ "scope": "write",
18920
+ "toolset": "accounting"
18921
+ },
18922
+ {
18923
+ "name": "list_quotes",
18924
+ "description": "List quotes with compact quote number, status, contact, dates, and totals.",
18925
+ "inputSchema": "schemas/list_records.json",
18926
+ "handler": "handlers/list_quotes.js",
18927
+ "scope": "read",
18928
+ "toolset": "accounting"
18929
+ },
18930
+ {
18931
+ "name": "create_quote",
18932
+ "description": "Create a quote from flat fields. Get contactId from list_contacts, accountCode from list_accounts, and taxType from list_tax_rates. Returns a compact summary and Xero link.",
18933
+ "inputSchema": "schemas/quote_write.json",
18934
+ "handler": "handlers/create_quote.js",
18935
+ "scope": "write",
18936
+ "toolset": "accounting"
18937
+ },
18938
+ {
18939
+ "name": "list_purchase_orders",
18940
+ "description": "List purchase orders with compact purchase order number, status, contact, delivery date, and totals.",
18941
+ "inputSchema": "schemas/list_records.json",
18942
+ "handler": "handlers/list_purchase_orders.js",
18943
+ "scope": "read",
18944
+ "toolset": "accounting"
18945
+ },
18946
+ {
18947
+ "name": "create_purchase_order",
18948
+ "description": "Create a purchase order from flat fields. Get supplier contactId from list_contacts and line account/tax values from list_accounts/list_tax_rates.",
18949
+ "inputSchema": "schemas/purchase_order_write.json",
18950
+ "handler": "handlers/create_purchase_order.js",
18951
+ "scope": "write",
18952
+ "toolset": "accounting"
18953
+ },
18954
+ {
18955
+ "name": "list_payments",
18956
+ "description": "List payments with compact payment ID, date, amount, status, account, and invoice references.",
18957
+ "inputSchema": "schemas/list_records.json",
18958
+ "handler": "handlers/list_payments.js",
18959
+ "scope": "read",
18960
+ "toolset": "accounting"
18961
+ },
18962
+ {
18963
+ "name": "create_payment",
18964
+ "description": "Create a payment against an invoice. Use get_invoice for invoiceId/amount due and list_accounts for the payment accountId. Returns a compact summary and Xero link.",
18965
+ "inputSchema": "schemas/payment_write.json",
18966
+ "handler": "handlers/create_payment.js",
18967
+ "scope": "write",
18968
+ "toolset": "accounting"
18969
+ },
18970
+ {
18971
+ "name": "list_bank_transactions",
18972
+ "description": "List bank transactions with compact type, status, contact, date, total, and bank account details.",
18973
+ "inputSchema": "schemas/list_records.json",
18974
+ "handler": "handlers/list_bank_transactions.js",
18975
+ "scope": "read",
18976
+ "toolset": "accounting"
18977
+ },
18978
+ {
18979
+ "name": "create_bank_transaction",
18980
+ "description": "Create a spend or receive bank transaction. Use list_accounts for valid bank/account codes and tax rates before calling this.",
18981
+ "inputSchema": "schemas/bank_transaction_write.json",
18982
+ "handler": "handlers/create_bank_transaction.js",
18983
+ "scope": "write",
18984
+ "toolset": "accounting"
18985
+ },
18986
+ {
18987
+ "name": "list_manual_journals",
18988
+ "description": "List manual journals with compact status, narration, date, journal ID, and line count.",
18989
+ "inputSchema": "schemas/list_records.json",
18990
+ "handler": "handlers/list_manual_journals.js",
18991
+ "scope": "read",
18992
+ "toolset": "accounting"
18993
+ },
18994
+ {
18995
+ "name": "create_manual_journal",
18996
+ "description": "Create a manual journal. Use this carefully; discover account codes with list_accounts and prefer DRAFT when supported by the organisation workflow.",
18997
+ "inputSchema": "schemas/manual_journal_write.json",
18998
+ "handler": "handlers/create_manual_journal.js",
18999
+ "scope": "write",
19000
+ "toolset": "accounting"
19001
+ },
19002
+ {
19003
+ "name": "list_attachments",
19004
+ "description": "List attachments for a supported Xero resource such as Invoices, Contacts, BankTransactions, CreditNotes, PurchaseOrders, or ManualJournals.",
19005
+ "inputSchema": "schemas/attachments.json",
19006
+ "handler": "handlers/list_attachments.js",
19007
+ "scope": "read",
19008
+ "toolset": "accounting"
19009
+ },
19010
+ {
19011
+ "name": "read_attachment_content",
19012
+ "description": "Extract readable text from an attachment on a supported Xero resource. Uses the shared file extractor for PDFs, Office files, CSV, text, HTML, and similar formats.",
19013
+ "inputSchema": "schemas/read_attachment_content.json",
19014
+ "handler": "handlers/read_attachment_content.js",
19015
+ "scope": "read",
19016
+ "toolset": "accounting"
19017
+ },
19018
+ {
19019
+ "name": "get_profit_and_loss",
19020
+ "description": "Get the Profit and Loss report for a date range or period. Returns Xero's report rows plus helper metadata for the query used.",
19021
+ "inputSchema": "schemas/report_profit_and_loss.json",
19022
+ "handler": "handlers/get_profit_and_loss.js",
19023
+ "scope": "read",
19024
+ "toolset": "reports"
19025
+ },
19026
+ {
19027
+ "name": "get_balance_sheet",
19028
+ "description": "Get the Balance Sheet report for a date or period. Use this for financial position summaries.",
19029
+ "inputSchema": "schemas/report_balance_sheet.json",
19030
+ "handler": "handlers/get_balance_sheet.js",
19031
+ "scope": "read",
19032
+ "toolset": "reports"
19033
+ },
19034
+ {
19035
+ "name": "get_trial_balance",
19036
+ "description": "Get the Trial Balance report at a point in time.",
19037
+ "inputSchema": "schemas/report_date.json",
19038
+ "handler": "handlers/get_trial_balance.js",
19039
+ "scope": "read",
19040
+ "toolset": "reports"
19041
+ },
19042
+ {
19043
+ "name": "get_bank_summary",
19044
+ "description": "Get the Bank Summary report for a date range.",
19045
+ "inputSchema": "schemas/report_date_range.json",
19046
+ "handler": "handlers/get_bank_summary.js",
19047
+ "scope": "read",
19048
+ "toolset": "reports"
19049
+ },
19050
+ {
19051
+ "name": "get_aged_payables_by_contact",
19052
+ "description": "Get the Aged Payables by Contact report for a supplier contact. Provide contactId from list_contacts.",
19053
+ "inputSchema": "schemas/report_aged_by_contact.json",
19054
+ "handler": "handlers/get_aged_payables_by_contact.js",
19055
+ "scope": "read",
19056
+ "toolset": "reports"
19057
+ },
19058
+ {
19059
+ "name": "get_aged_receivables_by_contact",
19060
+ "description": "Get the Aged Receivables by Contact report for a customer contact. Provide contactId from list_contacts.",
19061
+ "inputSchema": "schemas/report_aged_by_contact.json",
19062
+ "handler": "handlers/get_aged_receivables_by_contact.js",
19063
+ "scope": "read",
19064
+ "toolset": "reports"
19065
+ },
19066
+ {
19067
+ "name": "get_budget_summary",
19068
+ "description": "Get the Budget Summary report for a date range and period count.",
19069
+ "inputSchema": "schemas/report_budget_summary.json",
19070
+ "handler": "handlers/get_budget_summary.js",
19071
+ "scope": "read",
19072
+ "toolset": "reports"
19073
+ }
19074
+ ]
18672
19075
  },
18673
- preprocess: typeof variant.preprocess === "object" && variant.preprocess !== null ? {
18674
- ...variant.preprocess,
18675
- allowedOrigins: Array.isArray(variant.preprocess.allowedOrigins) ? [...variant.preprocess.allowedOrigins] : void 0
18676
- } : variant.preprocess,
18677
- healthCheck: "path" in variant.healthCheck ? { ...variant.healthCheck } : { notViable: true }
18678
- };
18679
- }
18680
- function validateCredentialVariant(type, variantKey, variant) {
18681
- const healthCheck = variant.healthCheck;
18682
- const hasHealthCheckPath = "path" in healthCheck && typeof healthCheck.path === "string" && healthCheck.path.trim().length > 0;
18683
- const healthCheckNotViable = "notViable" in healthCheck && healthCheck.notViable === true;
18684
- if (hasHealthCheckPath === healthCheckNotViable) {
18685
- throw new Error(`Invalid credentials config for '${type}/${variantKey}': declare exactly one of 'healthCheck.path' or 'healthCheck.notViable: true'.`);
18686
- }
18687
- }
18688
- function validateCredentialVariantsFile(type, raw) {
18689
- if (!(raw == null ? void 0 : raw.variants) || typeof raw.variants !== "object")
18690
- throw new Error(`Invalid credentials config for '${type}': missing variants object.`);
18691
- for (const [variantKey, variant] of Object.entries(raw.variants))
18692
- validateCredentialVariant(type, variantKey, variant);
18693
- return raw;
18694
- }
18695
- function cloneCredentialVariantsFile(type, variants) {
19076
+ "usageGuide": "## Authentication and tenants\n\nCustom Connections are single-organisation connections. Omit `tenantId` for those tools unless Xero explicitly gives you one. Public OAuth integrations can connect to multiple organisations; call `list_connections`, choose the intended tenant, then pass that `tenantId` to Accounting API tools.\n\n## Recommended workflows\n\n- Discovery before writes: call `get_organisation`, `list_accounts`, `list_tax_rates`, and `list_tracking_categories` before creating invoices, payments, bank transactions, or journals.\n- Contacts and items: use `list_contacts`/`get_contact` and `list_items`/`get_item` to find IDs before referencing them from invoices or payments.\n- Invoices: create draft invoices first where possible, then use `get_invoice` to inspect Xero's calculated totals and validation state before updating status.\n- Payments: call `get_invoice` and `list_accounts` first so the payment amount and account reference are valid.\n- Reports: report tools require the matching granular report scope. If Xero returns insufficient scope, reconnect the Xero app with the report scope listed in the tool description or credential hint.\n\n## Query filters\n\nXero list endpoints accept API-specific `where` and `order` expressions. Keep filters narrow and prefer `page` pagination over unbounded reads. Date fields should use ISO dates (`YYYY-MM-DD`) unless the Xero endpoint documents another format.\n\n## Payroll\n\nPayroll APIs are regional and not enabled for every organisation. Verify the demo company region and payroll scopes in the Xero developer portal before adding payroll tools to a live workflow. This integration currently ships Accounting API tools first.\n",
19077
+ "variants": {
19078
+ "variants": {
19079
+ "custom_connection": {
19080
+ "label": "Custom Connection",
19081
+ "schema": {
19082
+ "type": "object",
19083
+ "properties": {
19084
+ "clientId": {
19085
+ "type": "string",
19086
+ "title": "Client ID",
19087
+ "description": "Xero app client ID for a Custom Connection."
19088
+ },
19089
+ "clientSecret": {
19090
+ "type": "string",
19091
+ "title": "Client Secret",
19092
+ "description": "Xero app client secret for the Custom Connection.",
19093
+ "format": "password"
19094
+ }
19095
+ },
19096
+ "required": [
19097
+ "clientId",
19098
+ "clientSecret"
19099
+ ],
19100
+ "additionalProperties": false
19101
+ },
19102
+ "preprocess": {
19103
+ "type": "handler",
19104
+ "handlerCode": "async (creds, utils) => {\n const clientId = String(creds?.clientId || '').trim()\n const clientSecret = String(creds?.clientSecret || '').trim()\n\n if (!clientId)\n throw new Error('Missing clientId')\n if (!clientSecret)\n throw new Error('Missing clientSecret')\n\n const response = await utils.tokenFetch('https://identity.xero.com/connect/token', {\n method: 'POST',\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: clientId,\n client_secret: clientSecret,\n }),\n })\n\n const data = await response.json()\n if (!response.ok) {\n const message = typeof data?.error_description === 'string'\n ? data.error_description\n : (typeof data?.error === 'string' ? data.error : `Token request failed with status ${response.status}`)\n throw new Error(message)\n }\n\n const token = typeof data?.access_token === 'string' ? data.access_token : ''\n if (!token)\n throw new Error('Xero token response did not include access_token')\n\n return {\n token,\n expiresIn: data?.expires_in,\n }\n}",
19105
+ "allowedOrigins": [
19106
+ "https://identity.xero.com"
19107
+ ]
19108
+ },
19109
+ "injection": {
19110
+ "headers": {
19111
+ "Authorization": "Bearer {{token}}",
19112
+ "Accept": "application/json"
19113
+ }
19114
+ },
19115
+ "healthCheck": {
19116
+ "path": "/connections",
19117
+ "description": "Validates that the Custom Connection credentials can obtain a Xero access token."
19118
+ }
19119
+ }
19120
+ },
19121
+ "default": "custom_connection"
19122
+ },
19123
+ "hint": "1. Go to https://developer.xero.com/ and sign in with your free Xero developer account.\n2. Open My Apps, create an app, and choose a Custom Connection when testing against the Xero demo company.\n3. Authorise the Custom Connection for the demo company. Demo-company Custom Connections can be used for development testing without charge.\n4. Copy the Client ID and Client Secret into Commandable.\n5. Ensure the app is authorised for the Accounting API scopes used by this integration.\n6. For future public OAuth apps, use `offline_access`, store refresh tokens securely, and select a tenant from the `/connections` response before calling tenant-scoped Accounting API tools.",
19124
+ "hintsByVariant": {},
19125
+ "tools": [
19126
+ {
19127
+ "name": "list_connections",
19128
+ "description": "List Xero tenant connections available to the current token. Use this for public OAuth integrations to discover tenantId values; Custom Connections are single-organisation and usually do not require tenantId.",
19129
+ "inputSchema": {
19130
+ "$schema": "http://json-schema.org/draft-07/schema#",
19131
+ "type": "object",
19132
+ "properties": {},
19133
+ "additionalProperties": false
19134
+ },
19135
+ "handlerCode": "async () => {\n const res = await integration.get('/connections')\n const data = await res.json()\n const connections = Array.isArray(data) ? data : []\n\n return {\n connections: connections.map(connection => ({\n id: connection.id,\n tenantId: connection.tenantId,\n tenantName: connection.tenantName,\n tenantType: connection.tenantType,\n createdDateUtc: connection.createdDateUtc,\n updatedDateUtc: connection.updatedDateUtc,\n })),\n note: 'For public OAuth, pass tenantId to tenant-scoped tools. Custom Connections are single-organisation and may not need this tool.',\n }\n}",
19136
+ "scope": "read"
19137
+ },
19138
+ {
19139
+ "name": "get_organisation",
19140
+ "description": "Get the connected Xero organisation profile and settings. For public OAuth, provide tenantId from list_connections; for Custom Connections omit tenantId.",
19141
+ "inputSchema": {
19142
+ "$schema": "http://json-schema.org/draft-07/schema#",
19143
+ "type": "object",
19144
+ "properties": {
19145
+ "tenantId": {
19146
+ "type": "string",
19147
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19148
+ }
19149
+ },
19150
+ "additionalProperties": false
19151
+ },
19152
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get('/api.xro/2.0/Organisation', { headers })\n const data = await res.json()\n const organisations = Array.isArray(data?.Organisations) ? data.Organisations : []\n\n return {\n organisations: organisations.map(org => ({\n organisationId: org.OrganisationID,\n name: org.Name,\n legalName: org.LegalName,\n paysTax: org.PaysTax,\n version: org.Version,\n organisationType: org.OrganisationType,\n baseCurrency: org.BaseCurrency,\n countryCode: org.CountryCode,\n shortCode: org.ShortCode,\n timezone: org.Timezone,\n financialYearEndDay: org.FinancialYearEndDay,\n financialYearEndMonth: org.FinancialYearEndMonth,\n salesTaxBasis: org.SalesTaxBasis,\n salesTaxPeriod: org.SalesTaxPeriod,\n defaultSalesTax: org.DefaultSalesTax,\n defaultPurchasesTax: org.DefaultPurchasesTax,\n })),\n }\n}",
19153
+ "scope": "read"
19154
+ },
19155
+ {
19156
+ "name": "list_accounts",
19157
+ "description": "List chart of accounts with account IDs, codes, names, types, status, and tax type. Use this before creating payments, bank transactions, or journals.",
19158
+ "inputSchema": {
19159
+ "$schema": "http://json-schema.org/draft-07/schema#",
19160
+ "type": "object",
19161
+ "properties": {
19162
+ "tenantId": {
19163
+ "type": "string",
19164
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19165
+ },
19166
+ "where": {
19167
+ "type": "string",
19168
+ "description": 'Xero where filter expression, e.g. Status=="ACTIVE".'
19169
+ },
19170
+ "order": {
19171
+ "type": "string",
19172
+ "description": "Xero order expression, e.g. Code ASC."
19173
+ }
19174
+ },
19175
+ "additionalProperties": false
19176
+ },
19177
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n const path = `/api.xro/2.0/Accounts${params.toString() ? `?${params}` : ''}`\n const res = await integration.get(path, { headers })\n const data = await res.json()\n const accounts = Array.isArray(data?.Accounts) ? data.Accounts : []\n\n return {\n accounts: accounts.map(account => ({\n accountId: account.AccountID,\n code: account.Code,\n name: account.Name,\n type: account.Type,\n class: account.Class,\n status: account.Status,\n taxType: account.TaxType,\n enablePaymentsToAccount: account.EnablePaymentsToAccount,\n bankAccountNumber: account.BankAccountNumber,\n currencyCode: account.CurrencyCode,\n })),\n count: accounts.length,\n }\n}",
19178
+ "scope": "read"
19179
+ },
19180
+ {
19181
+ "name": "list_tax_rates",
19182
+ "description": "List tax rates available in the connected organisation. Use this to discover valid tax type values for invoice, item, and bank transaction lines.",
19183
+ "inputSchema": {
19184
+ "$schema": "http://json-schema.org/draft-07/schema#",
19185
+ "type": "object",
19186
+ "properties": {
19187
+ "tenantId": {
19188
+ "type": "string",
19189
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19190
+ }
19191
+ },
19192
+ "additionalProperties": false
19193
+ },
19194
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get('/api.xro/2.0/TaxRates', { headers })\n const data = await res.json()\n const taxRates = Array.isArray(data?.TaxRates) ? data.TaxRates : []\n\n return {\n taxRates: taxRates.map(rate => ({\n name: rate.Name,\n taxType: rate.TaxType,\n status: rate.Status,\n displayTaxRate: rate.DisplayTaxRate,\n effectiveRate: rate.EffectiveRate,\n canApplyToAssets: rate.CanApplyToAssets,\n canApplyToEquity: rate.CanApplyToEquity,\n canApplyToExpenses: rate.CanApplyToExpenses,\n canApplyToLiabilities: rate.CanApplyToLiabilities,\n canApplyToRevenue: rate.CanApplyToRevenue,\n })),\n count: taxRates.length,\n }\n}",
19195
+ "scope": "read"
19196
+ },
19197
+ {
19198
+ "name": "list_tracking_categories",
19199
+ "description": "List tracking categories and active options. Use this before adding tracking to invoice, journal, or bank transaction lines.",
19200
+ "inputSchema": {
19201
+ "$schema": "http://json-schema.org/draft-07/schema#",
19202
+ "type": "object",
19203
+ "properties": {
19204
+ "tenantId": {
19205
+ "type": "string",
19206
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19207
+ }
19208
+ },
19209
+ "additionalProperties": false
19210
+ },
19211
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get('/api.xro/2.0/TrackingCategories', { headers })\n const data = await res.json()\n const categories = Array.isArray(data?.TrackingCategories) ? data.TrackingCategories : []\n\n return {\n trackingCategories: categories.map(category => ({\n trackingCategoryId: category.TrackingCategoryID,\n name: category.Name,\n status: category.Status,\n options: Array.isArray(category.Options)\n ? category.Options.map(option => ({\n trackingOptionId: option.TrackingOptionID,\n name: option.Name,\n status: option.Status,\n }))\n : [],\n })),\n count: categories.length,\n }\n}",
19212
+ "scope": "read"
19213
+ },
19214
+ {
19215
+ "name": "list_contact_groups",
19216
+ "description": "List contact groups, or provide contactGroupId to fetch one group with contacts. Use this when users ask about Xero customer/supplier groupings.",
19217
+ "inputSchema": {
19218
+ "$schema": "http://json-schema.org/draft-07/schema#",
19219
+ "type": "object",
19220
+ "properties": {
19221
+ "tenantId": {
19222
+ "type": "string",
19223
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19224
+ },
19225
+ "contactGroupId": {
19226
+ "type": "string",
19227
+ "description": "Optional contact group ID to retrieve one group with contacts."
19228
+ }
19229
+ },
19230
+ "additionalProperties": false
19231
+ },
19232
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const path = input.contactGroupId\n ? `/api.xro/2.0/ContactGroups/${encodeURIComponent(input.contactGroupId)}`\n : '/api.xro/2.0/ContactGroups'\n const res = await integration.get(path, { headers })\n const data = await res.json()\n const contactGroups = Array.isArray(data?.ContactGroups) ? data.ContactGroups : []\n\n return {\n contactGroups: contactGroups.map(group => ({\n contactGroupId: group.ContactGroupID,\n name: group.Name,\n status: group.Status,\n contacts: Array.isArray(group.Contacts)\n ? group.Contacts.map(contact => ({\n contactId: contact.ContactID,\n name: contact.Name,\n emailAddress: contact.EmailAddress,\n }))\n : [],\n })),\n }\n}",
19233
+ "scope": "read",
19234
+ "toolset": "accounting"
19235
+ },
19236
+ {
19237
+ "name": "create_tracking_category",
19238
+ "description": "Create a tracking category. Use list_tracking_categories first to avoid duplicates.",
19239
+ "inputSchema": {
19240
+ "$schema": "http://json-schema.org/draft-07/schema#",
19241
+ "type": "object",
19242
+ "properties": {
19243
+ "tenantId": {
19244
+ "type": "string",
19245
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19246
+ },
19247
+ "trackingCategoryId": {
19248
+ "type": "string",
19249
+ "description": "Required for update_tracking_category. Get from list_tracking_categories."
19250
+ },
19251
+ "name": {
19252
+ "type": "string",
19253
+ "description": "Tracking category name."
19254
+ },
19255
+ "status": {
19256
+ "type": "string",
19257
+ "enum": [
19258
+ "ACTIVE",
19259
+ "ARCHIVED"
19260
+ ],
19261
+ "description": "Optional status for update_tracking_category."
19262
+ }
19263
+ },
19264
+ "additionalProperties": false
19265
+ },
19266
+ "handlerCode": "async (input) => {\n if (!input.name)\n throw new Error('name is required for create_tracking_category')\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.put('/api.xro/2.0/TrackingCategories', { Name: input.name }, { headers })\n const data = await res.json()\n const category = Array.isArray(data?.TrackingCategories) ? data.TrackingCategories[0] : null\n\n return {\n trackingCategory: category\n ? {\n trackingCategoryId: category.TrackingCategoryID,\n name: category.Name,\n status: category.Status,\n options: Array.isArray(category.Options)\n ? category.Options.map(option => ({\n trackingOptionId: option.TrackingOptionID,\n name: option.Name,\n status: option.Status,\n }))\n : [],\n }\n : null,\n }\n}",
19267
+ "scope": "write",
19268
+ "toolset": "accounting"
19269
+ },
19270
+ {
19271
+ "name": "create_tracking_options",
19272
+ "description": "Create up to 10 tracking options in a tracking category. Get trackingCategoryId from list_tracking_categories.",
19273
+ "inputSchema": {
19274
+ "$schema": "http://json-schema.org/draft-07/schema#",
19275
+ "type": "object",
19276
+ "properties": {
19277
+ "tenantId": {
19278
+ "type": "string",
19279
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19280
+ },
19281
+ "trackingCategoryId": {
19282
+ "type": "string",
19283
+ "description": "Tracking category ID from list_tracking_categories."
19284
+ },
19285
+ "optionNames": {
19286
+ "type": "array",
19287
+ "description": "Names of tracking options to create. Maximum 10 per call.",
19288
+ "items": {
19289
+ "type": "string"
19290
+ },
19291
+ "minItems": 1,
19292
+ "maxItems": 10
19293
+ },
19294
+ "trackingOptionId": {
19295
+ "type": "string",
19296
+ "description": "Required for update_tracking_options when updating one existing option."
19297
+ },
19298
+ "name": {
19299
+ "type": "string",
19300
+ "description": "New option name for update_tracking_options."
19301
+ },
19302
+ "status": {
19303
+ "type": "string",
19304
+ "enum": [
19305
+ "ACTIVE",
19306
+ "ARCHIVED"
19307
+ ],
19308
+ "description": "Optional option status for update_tracking_options."
19309
+ }
19310
+ },
19311
+ "additionalProperties": false
19312
+ },
19313
+ "handlerCode": "async (input) => {\n if (!input.trackingCategoryId)\n throw new Error('trackingCategoryId is required for create_tracking_options')\n if (!Array.isArray(input.optionNames) || !input.optionNames.length)\n throw new Error('optionNames is required for create_tracking_options')\n if (input.optionNames.length > 10)\n throw new Error('create_tracking_options accepts at most 10 optionNames per call')\n\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const created = []\n for (const optionName of input.optionNames) {\n const res = await integration.put(\n `/api.xro/2.0/TrackingCategories/${encodeURIComponent(input.trackingCategoryId)}/Options`,\n { Name: optionName },\n { headers },\n )\n const data = await res.json()\n const option = Array.isArray(data?.Options) ? data.Options[0] : null\n if (option) {\n created.push({\n trackingOptionId: option.TrackingOptionID,\n name: option.Name,\n status: option.Status,\n })\n }\n }\n\n return {\n createdCount: created.length,\n requestedCount: input.optionNames.length,\n trackingOptions: created,\n }\n}",
19314
+ "scope": "write",
19315
+ "toolset": "accounting"
19316
+ },
19317
+ {
19318
+ "name": "update_tracking_category",
19319
+ "description": "Rename or archive a tracking category. Get trackingCategoryId from list_tracking_categories.",
19320
+ "inputSchema": {
19321
+ "$schema": "http://json-schema.org/draft-07/schema#",
19322
+ "type": "object",
19323
+ "properties": {
19324
+ "tenantId": {
19325
+ "type": "string",
19326
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19327
+ },
19328
+ "trackingCategoryId": {
19329
+ "type": "string",
19330
+ "description": "Required for update_tracking_category. Get from list_tracking_categories."
19331
+ },
19332
+ "name": {
19333
+ "type": "string",
19334
+ "description": "Tracking category name."
19335
+ },
19336
+ "status": {
19337
+ "type": "string",
19338
+ "enum": [
19339
+ "ACTIVE",
19340
+ "ARCHIVED"
19341
+ ],
19342
+ "description": "Optional status for update_tracking_category."
19343
+ }
19344
+ },
19345
+ "additionalProperties": false
19346
+ },
19347
+ "handlerCode": "async (input) => {\n if (!input.trackingCategoryId)\n throw new Error('trackingCategoryId is required for update_tracking_category')\n if (!input.name && !input.status)\n throw new Error('Provide name or status for update_tracking_category')\n\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const body = {\n ...(input.name ? { Name: input.name } : {}),\n ...(input.status ? { Status: input.status } : {}),\n }\n const res = await integration.post(\n `/api.xro/2.0/TrackingCategories/${encodeURIComponent(input.trackingCategoryId)}`,\n body,\n { headers },\n )\n const data = await res.json()\n const category = Array.isArray(data?.TrackingCategories) ? data.TrackingCategories[0] : null\n\n return {\n trackingCategory: category\n ? {\n trackingCategoryId: category.TrackingCategoryID,\n name: category.Name,\n status: category.Status,\n options: Array.isArray(category.Options)\n ? category.Options.map(option => ({\n trackingOptionId: option.TrackingOptionID,\n name: option.Name,\n status: option.Status,\n }))\n : [],\n }\n : null,\n }\n}",
19348
+ "scope": "write",
19349
+ "toolset": "accounting"
19350
+ },
19351
+ {
19352
+ "name": "update_tracking_options",
19353
+ "description": "Rename or archive a tracking option. Get trackingCategoryId and trackingOptionId from list_tracking_categories.",
19354
+ "inputSchema": {
19355
+ "$schema": "http://json-schema.org/draft-07/schema#",
19356
+ "type": "object",
19357
+ "properties": {
19358
+ "tenantId": {
19359
+ "type": "string",
19360
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19361
+ },
19362
+ "trackingCategoryId": {
19363
+ "type": "string",
19364
+ "description": "Tracking category ID from list_tracking_categories."
19365
+ },
19366
+ "optionNames": {
19367
+ "type": "array",
19368
+ "description": "Names of tracking options to create. Maximum 10 per call.",
19369
+ "items": {
19370
+ "type": "string"
19371
+ },
19372
+ "minItems": 1,
19373
+ "maxItems": 10
19374
+ },
19375
+ "trackingOptionId": {
19376
+ "type": "string",
19377
+ "description": "Required for update_tracking_options when updating one existing option."
19378
+ },
19379
+ "name": {
19380
+ "type": "string",
19381
+ "description": "New option name for update_tracking_options."
19382
+ },
19383
+ "status": {
19384
+ "type": "string",
19385
+ "enum": [
19386
+ "ACTIVE",
19387
+ "ARCHIVED"
19388
+ ],
19389
+ "description": "Optional option status for update_tracking_options."
19390
+ }
19391
+ },
19392
+ "additionalProperties": false
19393
+ },
19394
+ "handlerCode": "async (input) => {\n if (!input.trackingCategoryId)\n throw new Error('trackingCategoryId is required for update_tracking_options')\n if (!input.trackingOptionId)\n throw new Error('trackingOptionId is required for update_tracking_options')\n if (!input.name && !input.status)\n throw new Error('Provide name or status for update_tracking_options')\n\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const body = {\n ...(input.name ? { Name: input.name } : {}),\n ...(input.status ? { Status: input.status } : {}),\n }\n const res = await integration.post(\n `/api.xro/2.0/TrackingCategories/${encodeURIComponent(input.trackingCategoryId)}/Options/${encodeURIComponent(input.trackingOptionId)}`,\n body,\n { headers },\n )\n const data = await res.json()\n const option = Array.isArray(data?.Options) ? data.Options[0] : null\n\n return {\n trackingOption: option\n ? {\n trackingOptionId: option.TrackingOptionID,\n name: option.Name,\n status: option.Status,\n }\n : null,\n }\n}",
19395
+ "scope": "write",
19396
+ "toolset": "accounting"
19397
+ },
19398
+ {
19399
+ "name": "list_currencies",
19400
+ "description": "List currencies configured in the connected Xero organisation.",
19401
+ "inputSchema": {
19402
+ "$schema": "http://json-schema.org/draft-07/schema#",
19403
+ "type": "object",
19404
+ "properties": {
19405
+ "tenantId": {
19406
+ "type": "string",
19407
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19408
+ }
19409
+ },
19410
+ "additionalProperties": false
19411
+ },
19412
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get('/api.xro/2.0/Currencies', { headers })\n const data = await res.json()\n const currencies = Array.isArray(data?.Currencies) ? data.Currencies : []\n\n return {\n currencies: currencies.map(currency => ({\n code: currency.Code,\n description: currency.Description,\n })),\n count: currencies.length,\n }\n}",
19413
+ "scope": "read"
19414
+ },
19415
+ {
19416
+ "name": "list_contacts",
19417
+ "description": "List contacts with compact identity and balance fields. Filter with where/order/page when needed; use get_contact for full details before updating.",
19418
+ "inputSchema": {
19419
+ "$schema": "http://json-schema.org/draft-07/schema#",
19420
+ "type": "object",
19421
+ "properties": {
19422
+ "tenantId": {
19423
+ "type": "string",
19424
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19425
+ },
19426
+ "page": {
19427
+ "type": "integer",
19428
+ "minimum": 1,
19429
+ "description": "Xero page number for paginated endpoints."
19430
+ },
19431
+ "where": {
19432
+ "type": "string",
19433
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
19434
+ },
19435
+ "order": {
19436
+ "type": "string",
19437
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
19438
+ },
19439
+ "modifiedAfter": {
19440
+ "type": "string",
19441
+ "description": "Only include records modified after this RFC3339 timestamp."
19442
+ },
19443
+ "status": {
19444
+ "type": "string",
19445
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
19446
+ },
19447
+ "contactIds": {
19448
+ "type": "array",
19449
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
19450
+ "items": {
19451
+ "type": "string"
19452
+ }
19453
+ },
19454
+ "invoiceNumbers": {
19455
+ "type": "array",
19456
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
19457
+ "items": {
19458
+ "type": "string"
19459
+ }
19460
+ },
19461
+ "fromDate": {
19462
+ "type": "string",
19463
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
19464
+ },
19465
+ "toDate": {
19466
+ "type": "string",
19467
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
19468
+ },
19469
+ "includeArchived": {
19470
+ "type": "boolean",
19471
+ "description": "Whether to include archived records when the endpoint supports it."
19472
+ }
19473
+ },
19474
+ "additionalProperties": false
19475
+ },
19476
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n if (input.includeArchived !== undefined) params.set('includeArchived', String(input.includeArchived))\n const res = await integration.get(`/api.xro/2.0/Contacts${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const contacts = Array.isArray(data?.Contacts) ? data.Contacts : []\n\n return {\n contacts: contacts.map(contact => ({\n contactId: contact.ContactID,\n name: contact.Name,\n emailAddress: contact.EmailAddress,\n contactStatus: contact.ContactStatus,\n isSupplier: contact.IsSupplier,\n isCustomer: contact.IsCustomer,\n balances: contact.Balances,\n updatedDateUtc: contact.UpdatedDateUTC,\n })),\n count: contacts.length,\n page: input.page || 1,\n next: contacts.length ? 'Call list_contacts with the next page number to continue.' : null,\n }\n}",
19477
+ "scope": "read",
19478
+ "toolset": "accounting"
19479
+ },
19480
+ {
19481
+ "name": "get_contact",
19482
+ "description": "Get a Xero contact by contactId. Use this before update_contact to avoid overwriting important fields.",
19483
+ "inputSchema": {
19484
+ "$schema": "http://json-schema.org/draft-07/schema#",
19485
+ "type": "object",
19486
+ "properties": {
19487
+ "tenantId": {
19488
+ "type": "string",
19489
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19490
+ },
19491
+ "id": {
19492
+ "type": "string",
19493
+ "description": "Xero resource ID."
19494
+ }
19495
+ },
19496
+ "required": [
19497
+ "id"
19498
+ ],
19499
+ "additionalProperties": false
19500
+ },
19501
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get(`/api.xro/2.0/Contacts/${encodeURIComponent(input.id)}`, { headers })\n const data = await res.json()\n const contacts = Array.isArray(data?.Contacts) ? data.Contacts : []\n const contact = contacts[0] || null\n\n return {\n contact: contact\n ? {\n contactId: contact.ContactID,\n name: contact.Name,\n emailAddress: contact.EmailAddress,\n firstName: contact.FirstName,\n lastName: contact.LastName,\n contactStatus: contact.ContactStatus,\n isSupplier: contact.IsSupplier,\n isCustomer: contact.IsCustomer,\n balances: contact.Balances,\n phones: contact.Phones,\n addresses: contact.Addresses,\n updatedDateUtc: contact.UpdatedDateUTC,\n }\n : null,\n }\n}",
19502
+ "scope": "read",
19503
+ "toolset": "accounting"
19504
+ },
19505
+ {
19506
+ "name": "create_contact",
19507
+ "description": "Create a contact from flat fields. Use extraFields only for advanced Xero fields; returns a compact summary and Xero link when available.",
19508
+ "inputSchema": {
19509
+ "$schema": "http://json-schema.org/draft-07/schema#",
19510
+ "type": "object",
19511
+ "properties": {
19512
+ "tenantId": {
19513
+ "type": "string",
19514
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19515
+ },
19516
+ "contactId": {
19517
+ "type": "string",
19518
+ "description": "Required for update_contact."
19519
+ },
19520
+ "name": {
19521
+ "type": "string",
19522
+ "description": "Contact name."
19523
+ },
19524
+ "emailAddress": {
19525
+ "type": "string",
19526
+ "description": "Primary email address."
19527
+ },
19528
+ "firstName": {
19529
+ "type": "string"
19530
+ },
19531
+ "lastName": {
19532
+ "type": "string"
19533
+ },
19534
+ "extraFields": {
19535
+ "type": "object",
19536
+ "description": "Additional Xero Contact fields. Values here override common fields.",
19537
+ "additionalProperties": true
19538
+ }
19539
+ },
19540
+ "additionalProperties": false
19541
+ },
19542
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeContact = async (contact) => {\n const contactId = contact?.ContactID || ''\n const shortCode = contactId ? await getShortCode() : ''\n return {\n contactId,\n name: contact?.Name,\n emailAddress: contact?.EmailAddress,\n contactStatus: contact?.ContactStatus,\n isCustomer: contact?.IsCustomer,\n isSupplier: contact?.IsSupplier,\n xeroUrl: shortCode && contactId ? `https://go.xero.com/app/${encodeURIComponent(shortCode)}/contacts/contact/${encodeURIComponent(contactId)}` : null,\n }\n }\n const contact = {\n ...(input.name ? { Name: input.name } : {}),\n ...(input.emailAddress ? { EmailAddress: input.emailAddress } : {}),\n ...(input.firstName ? { FirstName: input.firstName } : {}),\n ...(input.lastName ? { LastName: input.lastName } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/Contacts', { Contacts: [contact] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.Contacts) ? data.Contacts[0] : null\n return {\n contact: created ? await summarizeContact(created) : null,\n }\n}",
19543
+ "scope": "write",
19544
+ "toolset": "accounting"
19545
+ },
19546
+ {
19547
+ "name": "update_contact",
19548
+ "description": "Update a contact by contactId. Provide only fields to change; use get_contact first when preserving existing values matters.",
19549
+ "inputSchema": {
19550
+ "$schema": "http://json-schema.org/draft-07/schema#",
19551
+ "type": "object",
19552
+ "properties": {
19553
+ "tenantId": {
19554
+ "type": "string",
19555
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19556
+ },
19557
+ "contactId": {
19558
+ "type": "string",
19559
+ "description": "Required for update_contact."
19560
+ },
19561
+ "name": {
19562
+ "type": "string",
19563
+ "description": "Contact name."
19564
+ },
19565
+ "emailAddress": {
19566
+ "type": "string",
19567
+ "description": "Primary email address."
19568
+ },
19569
+ "firstName": {
19570
+ "type": "string"
19571
+ },
19572
+ "lastName": {
19573
+ "type": "string"
19574
+ },
19575
+ "extraFields": {
19576
+ "type": "object",
19577
+ "description": "Additional Xero Contact fields. Values here override common fields.",
19578
+ "additionalProperties": true
19579
+ }
19580
+ },
19581
+ "additionalProperties": false
19582
+ },
19583
+ "handlerCode": "async (input) => {\n if (!input.contactId)\n throw new Error('contactId is required for update_contact')\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeContact = async (contact) => {\n const contactId = contact?.ContactID || ''\n const shortCode = contactId ? await getShortCode() : ''\n return {\n contactId,\n name: contact?.Name,\n emailAddress: contact?.EmailAddress,\n contactStatus: contact?.ContactStatus,\n isCustomer: contact?.IsCustomer,\n isSupplier: contact?.IsSupplier,\n xeroUrl: shortCode && contactId ? `https://go.xero.com/app/${encodeURIComponent(shortCode)}/contacts/contact/${encodeURIComponent(contactId)}` : null,\n }\n }\n const contact = {\n ContactID: input.contactId,\n ...(input.name ? { Name: input.name } : {}),\n ...(input.emailAddress ? { EmailAddress: input.emailAddress } : {}),\n ...(input.firstName ? { FirstName: input.firstName } : {}),\n ...(input.lastName ? { LastName: input.lastName } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post(`/api.xro/2.0/Contacts/${encodeURIComponent(input.contactId)}`, { Contacts: [contact] }, { headers })\n const data = await res.json()\n const updated = Array.isArray(data?.Contacts) ? data.Contacts[0] : null\n return {\n contact: updated ? await summarizeContact(updated) : null,\n }\n}",
19584
+ "scope": "write",
19585
+ "toolset": "accounting"
19586
+ },
19587
+ {
19588
+ "name": "list_items",
19589
+ "description": "List inventory/service items with IDs, codes, names, status, and sale/purchase details. Use get_item for full details.",
19590
+ "inputSchema": {
19591
+ "$schema": "http://json-schema.org/draft-07/schema#",
19592
+ "type": "object",
19593
+ "properties": {
19594
+ "tenantId": {
19595
+ "type": "string",
19596
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19597
+ },
19598
+ "page": {
19599
+ "type": "integer",
19600
+ "minimum": 1,
19601
+ "description": "Xero page number for paginated endpoints."
19602
+ },
19603
+ "where": {
19604
+ "type": "string",
19605
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
19606
+ },
19607
+ "order": {
19608
+ "type": "string",
19609
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
19610
+ },
19611
+ "modifiedAfter": {
19612
+ "type": "string",
19613
+ "description": "Only include records modified after this RFC3339 timestamp."
19614
+ },
19615
+ "status": {
19616
+ "type": "string",
19617
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
19618
+ },
19619
+ "contactIds": {
19620
+ "type": "array",
19621
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
19622
+ "items": {
19623
+ "type": "string"
19624
+ }
19625
+ },
19626
+ "invoiceNumbers": {
19627
+ "type": "array",
19628
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
19629
+ "items": {
19630
+ "type": "string"
19631
+ }
19632
+ },
19633
+ "fromDate": {
19634
+ "type": "string",
19635
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
19636
+ },
19637
+ "toDate": {
19638
+ "type": "string",
19639
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
19640
+ },
19641
+ "includeArchived": {
19642
+ "type": "boolean",
19643
+ "description": "Whether to include archived records when the endpoint supports it."
19644
+ }
19645
+ },
19646
+ "additionalProperties": false
19647
+ },
19648
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/Items${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const items = Array.isArray(data?.Items) ? data.Items : []\n\n return {\n items: items.map(item => ({\n itemId: item.ItemID,\n code: item.Code,\n name: item.Name,\n description: item.Description,\n isTrackedAsInventory: item.IsTrackedAsInventory,\n isSold: item.IsSold,\n isPurchased: item.IsPurchased,\n salesDetails: item.SalesDetails,\n purchaseDetails: item.PurchaseDetails,\n updatedDateUtc: item.UpdatedDateUTC,\n })),\n count: items.length,\n page: input.page || 1,\n }\n}",
19649
+ "scope": "read",
19650
+ "toolset": "accounting"
19651
+ },
19652
+ {
19653
+ "name": "get_item",
19654
+ "description": "Get a Xero item by itemId.",
19655
+ "inputSchema": {
19656
+ "$schema": "http://json-schema.org/draft-07/schema#",
19657
+ "type": "object",
19658
+ "properties": {
19659
+ "tenantId": {
19660
+ "type": "string",
19661
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19662
+ },
19663
+ "id": {
19664
+ "type": "string",
19665
+ "description": "Xero resource ID."
19666
+ }
19667
+ },
19668
+ "required": [
19669
+ "id"
19670
+ ],
19671
+ "additionalProperties": false
19672
+ },
19673
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get(`/api.xro/2.0/Items/${encodeURIComponent(input.id)}`, { headers })\n const data = await res.json()\n const item = Array.isArray(data?.Items) ? data.Items[0] : null\n return {\n item: item\n ? {\n itemId: item.ItemID,\n code: item.Code,\n name: item.Name,\n description: item.Description,\n purchaseDescription: item.PurchaseDescription,\n isTrackedAsInventory: item.IsTrackedAsInventory,\n isSold: item.IsSold,\n isPurchased: item.IsPurchased,\n salesDetails: item.SalesDetails,\n purchaseDetails: item.PurchaseDetails,\n updatedDateUtc: item.UpdatedDateUTC,\n }\n : null,\n }\n}",
19674
+ "scope": "read",
19675
+ "toolset": "accounting"
19676
+ },
19677
+ {
19678
+ "name": "create_item",
19679
+ "description": "Create an item from flat fields. Use extraFields only for advanced Xero fields.",
19680
+ "inputSchema": {
19681
+ "$schema": "http://json-schema.org/draft-07/schema#",
19682
+ "type": "object",
19683
+ "properties": {
19684
+ "tenantId": {
19685
+ "type": "string",
19686
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19687
+ },
19688
+ "itemId": {
19689
+ "type": "string",
19690
+ "description": "Required for update_item."
19691
+ },
19692
+ "code": {
19693
+ "type": "string",
19694
+ "description": "Item code."
19695
+ },
19696
+ "name": {
19697
+ "type": "string",
19698
+ "description": "Item name."
19699
+ },
19700
+ "description": {
19701
+ "type": "string",
19702
+ "description": "Sales description."
19703
+ },
19704
+ "extraFields": {
19705
+ "type": "object",
19706
+ "description": "Additional Xero Item fields. Values here override common fields.",
19707
+ "additionalProperties": true
19708
+ }
19709
+ },
19710
+ "additionalProperties": false
19711
+ },
19712
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const summarizeItem = item => ({\n itemId: item?.ItemID,\n code: item?.Code,\n name: item?.Name,\n description: item?.Description,\n isSold: item?.IsSold,\n isPurchased: item?.IsPurchased,\n })\n const item = {\n ...(input.code ? { Code: input.code } : {}),\n ...(input.name ? { Name: input.name } : {}),\n ...(input.description ? { Description: input.description } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/Items', { Items: [item] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.Items) ? data.Items[0] : null\n return {\n item: created ? summarizeItem(created) : null,\n }\n}",
19713
+ "scope": "write",
19714
+ "toolset": "accounting"
19715
+ },
19716
+ {
19717
+ "name": "update_item",
19718
+ "description": "Update an item by itemId. Provide only fields to change.",
19719
+ "inputSchema": {
19720
+ "$schema": "http://json-schema.org/draft-07/schema#",
19721
+ "type": "object",
19722
+ "properties": {
19723
+ "tenantId": {
19724
+ "type": "string",
19725
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19726
+ },
19727
+ "itemId": {
19728
+ "type": "string",
19729
+ "description": "Required for update_item."
19730
+ },
19731
+ "code": {
19732
+ "type": "string",
19733
+ "description": "Item code."
19734
+ },
19735
+ "name": {
19736
+ "type": "string",
19737
+ "description": "Item name."
19738
+ },
19739
+ "description": {
19740
+ "type": "string",
19741
+ "description": "Sales description."
19742
+ },
19743
+ "extraFields": {
19744
+ "type": "object",
19745
+ "description": "Additional Xero Item fields. Values here override common fields.",
19746
+ "additionalProperties": true
19747
+ }
19748
+ },
19749
+ "additionalProperties": false
19750
+ },
19751
+ "handlerCode": "async (input) => {\n if (!input.itemId)\n throw new Error('itemId is required for update_item')\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const summarizeItem = item => ({\n itemId: item?.ItemID,\n code: item?.Code,\n name: item?.Name,\n description: item?.Description,\n isSold: item?.IsSold,\n isPurchased: item?.IsPurchased,\n })\n const item = {\n ItemID: input.itemId,\n ...(input.code ? { Code: input.code } : {}),\n ...(input.name ? { Name: input.name } : {}),\n ...(input.description ? { Description: input.description } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post(`/api.xro/2.0/Items/${encodeURIComponent(input.itemId)}`, { Items: [item] }, { headers })\n const data = await res.json()\n const updated = Array.isArray(data?.Items) ? data.Items[0] : null\n return {\n item: updated ? summarizeItem(updated) : null,\n }\n}",
19752
+ "scope": "write",
19753
+ "toolset": "accounting"
19754
+ },
19755
+ {
19756
+ "name": "list_invoices",
19757
+ "description": "List invoices with compact totals, status, contact, dates, and invoice number. Prefer explicit filters like status, contactIds, invoiceNumbers, fromDate/toDate; use get_invoice for line items.",
19758
+ "inputSchema": {
19759
+ "$schema": "http://json-schema.org/draft-07/schema#",
19760
+ "type": "object",
19761
+ "properties": {
19762
+ "tenantId": {
19763
+ "type": "string",
19764
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19765
+ },
19766
+ "page": {
19767
+ "type": "integer",
19768
+ "minimum": 1,
19769
+ "description": "Xero page number for paginated endpoints."
19770
+ },
19771
+ "where": {
19772
+ "type": "string",
19773
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
19774
+ },
19775
+ "order": {
19776
+ "type": "string",
19777
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
19778
+ },
19779
+ "modifiedAfter": {
19780
+ "type": "string",
19781
+ "description": "Only include records modified after this RFC3339 timestamp."
19782
+ },
19783
+ "status": {
19784
+ "type": "string",
19785
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
19786
+ },
19787
+ "contactIds": {
19788
+ "type": "array",
19789
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
19790
+ "items": {
19791
+ "type": "string"
19792
+ }
19793
+ },
19794
+ "invoiceNumbers": {
19795
+ "type": "array",
19796
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
19797
+ "items": {
19798
+ "type": "string"
19799
+ }
19800
+ },
19801
+ "fromDate": {
19802
+ "type": "string",
19803
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
19804
+ },
19805
+ "toDate": {
19806
+ "type": "string",
19807
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
19808
+ },
19809
+ "includeArchived": {
19810
+ "type": "boolean",
19811
+ "description": "Whether to include archived records when the endpoint supports it."
19812
+ }
19813
+ },
19814
+ "additionalProperties": false
19815
+ },
19816
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (Array.isArray(input.contactIds) && input.contactIds.length) params.set('ContactIDs', input.contactIds.join(','))\n if (Array.isArray(input.invoiceNumbers) && input.invoiceNumbers.length) params.set('InvoiceNumbers', input.invoiceNumbers.join(','))\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/Invoices${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const invoices = Array.isArray(data?.Invoices) ? data.Invoices : []\n\n return {\n invoices: invoices.map(invoice => ({\n invoiceId: invoice.InvoiceID,\n invoiceNumber: invoice.InvoiceNumber,\n type: invoice.Type,\n status: invoice.Status,\n contact: invoice.Contact ? { contactId: invoice.Contact.ContactID, name: invoice.Contact.Name } : null,\n date: invoice.DateString || invoice.Date,\n dueDate: invoice.DueDateString || invoice.DueDate,\n currencyCode: invoice.CurrencyCode,\n total: invoice.Total,\n amountDue: invoice.AmountDue,\n amountPaid: invoice.AmountPaid,\n updatedDateUtc: invoice.UpdatedDateUTC,\n })),\n count: invoices.length,\n page: input.page || 1,\n }\n}",
19817
+ "scope": "read",
19818
+ "toolset": "accounting"
19819
+ },
19820
+ {
19821
+ "name": "get_invoice",
19822
+ "description": "Get an invoice by invoiceId, including line items and payment summary.",
19823
+ "inputSchema": {
19824
+ "$schema": "http://json-schema.org/draft-07/schema#",
19825
+ "type": "object",
19826
+ "properties": {
19827
+ "tenantId": {
19828
+ "type": "string",
19829
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19830
+ },
19831
+ "id": {
19832
+ "type": "string",
19833
+ "description": "Xero resource ID."
19834
+ }
19835
+ },
19836
+ "required": [
19837
+ "id"
19838
+ ],
19839
+ "additionalProperties": false
19840
+ },
19841
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const res = await integration.get(`/api.xro/2.0/Invoices/${encodeURIComponent(input.id)}`, { headers })\n const data = await res.json()\n const invoice = Array.isArray(data?.Invoices) ? data.Invoices[0] : null\n const lineItems = Array.isArray(invoice?.LineItems)\n ? invoice.LineItems.map(line => ({\n lineItemId: line.LineItemID,\n description: line.Description,\n quantity: line.Quantity,\n unitAmount: line.UnitAmount,\n accountCode: line.AccountCode,\n taxType: line.TaxType,\n itemCode: line.ItemCode,\n lineAmount: line.LineAmount,\n tracking: line.Tracking,\n }))\n : []\n return {\n invoice: invoice\n ? {\n invoiceId: invoice.InvoiceID,\n invoiceNumber: invoice.InvoiceNumber,\n type: invoice.Type,\n status: invoice.Status,\n contact: invoice.Contact ? { contactId: invoice.Contact.ContactID, name: invoice.Contact.Name } : null,\n date: invoice.DateString || invoice.Date,\n dueDate: invoice.DueDateString || invoice.DueDate,\n currencyCode: invoice.CurrencyCode,\n subTotal: invoice.SubTotal,\n totalTax: invoice.TotalTax,\n total: invoice.Total,\n amountDue: invoice.AmountDue,\n amountPaid: invoice.AmountPaid,\n amountCredited: invoice.AmountCredited,\n reference: invoice.Reference,\n lineItems,\n payments: invoice.Payments,\n updatedDateUtc: invoice.UpdatedDateUTC,\n }\n : null,\n }\n}",
19842
+ "scope": "read",
19843
+ "toolset": "accounting"
19844
+ },
19845
+ {
19846
+ "name": "create_invoice",
19847
+ "description": "Create an invoice or bill from flat fields. Get contactId from list_contacts, accountCode from list_accounts, taxType from list_tax_rates, and tracking from list_tracking_categories. Returns a compact summary and Xero link.",
19848
+ "inputSchema": {
19849
+ "$schema": "http://json-schema.org/draft-07/schema#",
19850
+ "type": "object",
19851
+ "definitions": {
19852
+ "tracking": {
19853
+ "type": "object",
19854
+ "properties": {
19855
+ "name": {
19856
+ "type": "string",
19857
+ "description": "Tracking category name from list_tracking_categories."
19858
+ },
19859
+ "option": {
19860
+ "type": "string",
19861
+ "description": "Tracking option name from list_tracking_categories."
19862
+ },
19863
+ "trackingCategoryId": {
19864
+ "type": "string",
19865
+ "description": "Tracking category ID from list_tracking_categories."
19866
+ }
19867
+ },
19868
+ "required": [
19869
+ "name",
19870
+ "option"
19871
+ ],
19872
+ "additionalProperties": false
19873
+ },
19874
+ "lineItem": {
19875
+ "type": "object",
19876
+ "properties": {
19877
+ "description": {
19878
+ "type": "string",
19879
+ "description": "Line item description."
19880
+ },
19881
+ "quantity": {
19882
+ "type": "number",
19883
+ "description": "Line item quantity."
19884
+ },
19885
+ "unitAmount": {
19886
+ "type": "number",
19887
+ "description": "Price per unit."
19888
+ },
19889
+ "accountCode": {
19890
+ "type": "string",
19891
+ "description": "Account code from list_accounts."
19892
+ },
19893
+ "taxType": {
19894
+ "type": "string",
19895
+ "description": "Tax type from list_tax_rates."
19896
+ },
19897
+ "itemCode": {
19898
+ "type": "string",
19899
+ "description": "Optional item code from list_items."
19900
+ },
19901
+ "tracking": {
19902
+ "type": "array",
19903
+ "description": "Optional tracking categories. Use only when requested by the user.",
19904
+ "items": {
19905
+ "$ref": "#/definitions/tracking"
19906
+ },
19907
+ "maxItems": 2
19908
+ }
19909
+ },
19910
+ "required": [
19911
+ "description",
19912
+ "quantity",
19913
+ "unitAmount",
19914
+ "accountCode",
19915
+ "taxType"
19916
+ ],
19917
+ "additionalProperties": false
19918
+ }
19919
+ },
19920
+ "properties": {
19921
+ "tenantId": {
19922
+ "type": "string",
19923
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
19924
+ },
19925
+ "invoiceId": {
19926
+ "type": "string",
19927
+ "description": "Required for update_invoice."
19928
+ },
19929
+ "contactId": {
19930
+ "type": "string",
19931
+ "description": "Contact ID from list_contacts."
19932
+ },
19933
+ "lineItems": {
19934
+ "type": "array",
19935
+ "description": "Invoice line items. Use list_accounts and list_tax_rates to discover accountCode and taxType values.",
19936
+ "items": {
19937
+ "$ref": "#/definitions/lineItem"
19938
+ },
19939
+ "minItems": 1
19940
+ },
19941
+ "type": {
19942
+ "type": "string",
19943
+ "enum": [
19944
+ "ACCREC",
19945
+ "ACCPAY"
19946
+ ],
19947
+ "description": "ACCREC creates a sales/customer invoice. ACCPAY creates a purchase/supplier bill."
19948
+ },
19949
+ "reference": {
19950
+ "type": "string",
19951
+ "description": "Optional reference for the invoice or bill."
19952
+ },
19953
+ "date": {
19954
+ "type": "string",
19955
+ "description": "Invoice date in YYYY-MM-DD format. Defaults to today when omitted."
19956
+ },
19957
+ "dueDate": {
19958
+ "type": "string",
19959
+ "description": "Due date in YYYY-MM-DD format."
19960
+ },
19961
+ "status": {
19962
+ "type": "string",
19963
+ "enum": [
19964
+ "DRAFT",
19965
+ "SUBMITTED",
19966
+ "AUTHORISED"
19967
+ ],
19968
+ "description": "Invoice status. Prefer DRAFT unless the user explicitly asks otherwise."
19969
+ },
19970
+ "extraFields": {
19971
+ "type": "object",
19972
+ "description": "Advanced Xero Invoice fields to merge into the generated invoice object.",
19973
+ "additionalProperties": true
19974
+ }
19975
+ },
19976
+ "additionalProperties": false
19977
+ },
19978
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const defaultDueDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)\n const mapTracking = tracking => Array.isArray(tracking)\n ? tracking.map(item => ({\n Name: item.name,\n Option: item.option,\n ...(item.trackingCategoryId ? { TrackingCategoryID: item.trackingCategoryId } : {}),\n }))\n : undefined\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n ...(item.itemCode ? { ItemCode: item.itemCode } : {}),\n ...(item.tracking ? { Tracking: mapTracking(item.tracking) } : {}),\n })\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeInvoice = async (invoice) => {\n const invoiceId = invoice?.InvoiceID || ''\n const shortCode = invoiceId ? await getShortCode() : ''\n const isBill = invoice?.Type === 'ACCPAY'\n return {\n invoiceId,\n invoiceNumber: invoice?.InvoiceNumber,\n type: invoice?.Type,\n status: invoice?.Status,\n contact: invoice?.Contact ? { contactId: invoice.Contact.ContactID, name: invoice.Contact.Name } : null,\n date: invoice?.DateString || invoice?.Date,\n dueDate: invoice?.DueDateString || invoice?.DueDate,\n currencyCode: invoice?.CurrencyCode,\n total: invoice?.Total,\n amountDue: invoice?.AmountDue,\n lineItemCount: Array.isArray(invoice?.LineItems) ? invoice.LineItems.length : undefined,\n xeroUrl: shortCode && invoiceId\n ? (isBill\n ? `https://go.xero.com/organisationlogin/default.aspx?shortcode=${encodeURIComponent(shortCode)}&redirecturl=/AccountsPayable/Edit.aspx?InvoiceID=${encodeURIComponent(invoiceId)}`\n : `https://go.xero.com/app/${encodeURIComponent(shortCode)}/invoicing/view/${encodeURIComponent(invoiceId)}`)\n : null,\n }\n }\n if (!input.contactId)\n throw new Error('contactId is required for create_invoice')\n if (!Array.isArray(input.lineItems) || !input.lineItems.length)\n throw new Error('lineItems is required for create_invoice')\n const invoice = {\n Type: input.type || 'ACCREC',\n Contact: { ContactID: input.contactId },\n LineItems: input.lineItems.map(mapLineItem),\n Date: input.date || today,\n DueDate: input.dueDate || defaultDueDate,\n Status: input.status || 'DRAFT',\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/Invoices', { Invoices: [invoice] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.Invoices) ? data.Invoices[0] : null\n return {\n invoice: created ? await summarizeInvoice(created) : null,\n }\n}",
19979
+ "scope": "write",
19980
+ "toolset": "accounting"
19981
+ },
19982
+ {
19983
+ "name": "update_invoice",
19984
+ "description": "Update an invoice by invoiceId. Use this for draft changes or allowed status transitions; prefer get_invoice first.",
19985
+ "inputSchema": {
19986
+ "$schema": "http://json-schema.org/draft-07/schema#",
19987
+ "type": "object",
19988
+ "definitions": {
19989
+ "tracking": {
19990
+ "type": "object",
19991
+ "properties": {
19992
+ "name": {
19993
+ "type": "string",
19994
+ "description": "Tracking category name from list_tracking_categories."
19995
+ },
19996
+ "option": {
19997
+ "type": "string",
19998
+ "description": "Tracking option name from list_tracking_categories."
19999
+ },
20000
+ "trackingCategoryId": {
20001
+ "type": "string",
20002
+ "description": "Tracking category ID from list_tracking_categories."
20003
+ }
20004
+ },
20005
+ "required": [
20006
+ "name",
20007
+ "option"
20008
+ ],
20009
+ "additionalProperties": false
20010
+ },
20011
+ "lineItem": {
20012
+ "type": "object",
20013
+ "properties": {
20014
+ "description": {
20015
+ "type": "string",
20016
+ "description": "Line item description."
20017
+ },
20018
+ "quantity": {
20019
+ "type": "number",
20020
+ "description": "Line item quantity."
20021
+ },
20022
+ "unitAmount": {
20023
+ "type": "number",
20024
+ "description": "Price per unit."
20025
+ },
20026
+ "accountCode": {
20027
+ "type": "string",
20028
+ "description": "Account code from list_accounts."
20029
+ },
20030
+ "taxType": {
20031
+ "type": "string",
20032
+ "description": "Tax type from list_tax_rates."
20033
+ },
20034
+ "itemCode": {
20035
+ "type": "string",
20036
+ "description": "Optional item code from list_items."
20037
+ },
20038
+ "tracking": {
20039
+ "type": "array",
20040
+ "description": "Optional tracking categories. Use only when requested by the user.",
20041
+ "items": {
20042
+ "$ref": "#/definitions/tracking"
20043
+ },
20044
+ "maxItems": 2
20045
+ }
20046
+ },
20047
+ "required": [
20048
+ "description",
20049
+ "quantity",
20050
+ "unitAmount",
20051
+ "accountCode",
20052
+ "taxType"
20053
+ ],
20054
+ "additionalProperties": false
20055
+ }
20056
+ },
20057
+ "properties": {
20058
+ "tenantId": {
20059
+ "type": "string",
20060
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20061
+ },
20062
+ "invoiceId": {
20063
+ "type": "string",
20064
+ "description": "Required for update_invoice."
20065
+ },
20066
+ "contactId": {
20067
+ "type": "string",
20068
+ "description": "Contact ID from list_contacts."
20069
+ },
20070
+ "lineItems": {
20071
+ "type": "array",
20072
+ "description": "Invoice line items. Use list_accounts and list_tax_rates to discover accountCode and taxType values.",
20073
+ "items": {
20074
+ "$ref": "#/definitions/lineItem"
20075
+ },
20076
+ "minItems": 1
20077
+ },
20078
+ "type": {
20079
+ "type": "string",
20080
+ "enum": [
20081
+ "ACCREC",
20082
+ "ACCPAY"
20083
+ ],
20084
+ "description": "ACCREC creates a sales/customer invoice. ACCPAY creates a purchase/supplier bill."
20085
+ },
20086
+ "reference": {
20087
+ "type": "string",
20088
+ "description": "Optional reference for the invoice or bill."
20089
+ },
20090
+ "date": {
20091
+ "type": "string",
20092
+ "description": "Invoice date in YYYY-MM-DD format. Defaults to today when omitted."
20093
+ },
20094
+ "dueDate": {
20095
+ "type": "string",
20096
+ "description": "Due date in YYYY-MM-DD format."
20097
+ },
20098
+ "status": {
20099
+ "type": "string",
20100
+ "enum": [
20101
+ "DRAFT",
20102
+ "SUBMITTED",
20103
+ "AUTHORISED"
20104
+ ],
20105
+ "description": "Invoice status. Prefer DRAFT unless the user explicitly asks otherwise."
20106
+ },
20107
+ "extraFields": {
20108
+ "type": "object",
20109
+ "description": "Advanced Xero Invoice fields to merge into the generated invoice object.",
20110
+ "additionalProperties": true
20111
+ }
20112
+ },
20113
+ "additionalProperties": false
20114
+ },
20115
+ "handlerCode": "async (input) => {\n if (!input.invoiceId)\n throw new Error('invoiceId is required for update_invoice')\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const mapTracking = tracking => Array.isArray(tracking)\n ? tracking.map(item => ({\n Name: item.name,\n Option: item.option,\n ...(item.trackingCategoryId ? { TrackingCategoryID: item.trackingCategoryId } : {}),\n }))\n : undefined\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n ...(item.itemCode ? { ItemCode: item.itemCode } : {}),\n ...(item.tracking ? { Tracking: mapTracking(item.tracking) } : {}),\n })\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeInvoice = async (invoice) => {\n const invoiceId = invoice?.InvoiceID || ''\n const shortCode = invoiceId ? await getShortCode() : ''\n const isBill = invoice?.Type === 'ACCPAY'\n return {\n invoiceId,\n invoiceNumber: invoice?.InvoiceNumber,\n type: invoice?.Type,\n status: invoice?.Status,\n contact: invoice?.Contact ? { contactId: invoice.Contact.ContactID, name: invoice.Contact.Name } : null,\n date: invoice?.DateString || invoice?.Date,\n dueDate: invoice?.DueDateString || invoice?.DueDate,\n total: invoice?.Total,\n amountDue: invoice?.AmountDue,\n lineItemCount: Array.isArray(invoice?.LineItems) ? invoice.LineItems.length : undefined,\n xeroUrl: shortCode && invoiceId\n ? (isBill\n ? `https://go.xero.com/organisationlogin/default.aspx?shortcode=${encodeURIComponent(shortCode)}&redirecturl=/AccountsPayable/Edit.aspx?InvoiceID=${encodeURIComponent(invoiceId)}`\n : `https://go.xero.com/app/${encodeURIComponent(shortCode)}/invoicing/view/${encodeURIComponent(invoiceId)}`)\n : null,\n }\n }\n const invoice = {\n InvoiceID: input.invoiceId,\n ...(input.contactId ? { Contact: { ContactID: input.contactId } } : {}),\n ...(Array.isArray(input.lineItems) ? { LineItems: input.lineItems.map(mapLineItem) } : {}),\n ...(input.type ? { Type: input.type } : {}),\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.date ? { Date: input.date } : {}),\n ...(input.dueDate ? { DueDate: input.dueDate } : {}),\n ...(input.status ? { Status: input.status } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post(`/api.xro/2.0/Invoices/${encodeURIComponent(input.invoiceId)}`, { Invoices: [invoice] }, { headers })\n const data = await res.json()\n const updated = Array.isArray(data?.Invoices) ? data.Invoices[0] : null\n return {\n invoice: updated ? await summarizeInvoice(updated) : null,\n }\n}",
20116
+ "scope": "write",
20117
+ "toolset": "accounting"
20118
+ },
20119
+ {
20120
+ "name": "list_credit_notes",
20121
+ "description": "List credit notes with compact status, contact, date, total, and remaining credit fields.",
20122
+ "inputSchema": {
20123
+ "$schema": "http://json-schema.org/draft-07/schema#",
20124
+ "type": "object",
20125
+ "properties": {
20126
+ "tenantId": {
20127
+ "type": "string",
20128
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20129
+ },
20130
+ "page": {
20131
+ "type": "integer",
20132
+ "minimum": 1,
20133
+ "description": "Xero page number for paginated endpoints."
20134
+ },
20135
+ "where": {
20136
+ "type": "string",
20137
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20138
+ },
20139
+ "order": {
20140
+ "type": "string",
20141
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20142
+ },
20143
+ "modifiedAfter": {
20144
+ "type": "string",
20145
+ "description": "Only include records modified after this RFC3339 timestamp."
20146
+ },
20147
+ "status": {
20148
+ "type": "string",
20149
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20150
+ },
20151
+ "contactIds": {
20152
+ "type": "array",
20153
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20154
+ "items": {
20155
+ "type": "string"
20156
+ }
20157
+ },
20158
+ "invoiceNumbers": {
20159
+ "type": "array",
20160
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20161
+ "items": {
20162
+ "type": "string"
20163
+ }
20164
+ },
20165
+ "fromDate": {
20166
+ "type": "string",
20167
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20168
+ },
20169
+ "toDate": {
20170
+ "type": "string",
20171
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20172
+ },
20173
+ "includeArchived": {
20174
+ "type": "boolean",
20175
+ "description": "Whether to include archived records when the endpoint supports it."
20176
+ }
20177
+ },
20178
+ "additionalProperties": false
20179
+ },
20180
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/CreditNotes${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const creditNotes = Array.isArray(data?.CreditNotes) ? data.CreditNotes : []\n return {\n creditNotes: creditNotes.map(note => ({\n creditNoteId: note.CreditNoteID,\n creditNoteNumber: note.CreditNoteNumber,\n type: note.Type,\n status: note.Status,\n contact: note.Contact ? { contactId: note.Contact.ContactID, name: note.Contact.Name } : null,\n date: note.DateString || note.Date,\n total: note.Total,\n remainingCredit: note.RemainingCredit,\n updatedDateUtc: note.UpdatedDateUTC,\n })),\n count: creditNotes.length,\n page: input.page || 1,\n }\n}",
20181
+ "scope": "read",
20182
+ "toolset": "accounting"
20183
+ },
20184
+ {
20185
+ "name": "create_credit_note",
20186
+ "description": "Create a credit note from flat fields. Get contactId from list_contacts, accountCode from list_accounts, and taxType from list_tax_rates. Returns a compact summary and Xero link.",
20187
+ "inputSchema": {
20188
+ "$schema": "http://json-schema.org/draft-07/schema#",
20189
+ "type": "object",
20190
+ "definitions": {
20191
+ "lineItem": {
20192
+ "type": "object",
20193
+ "properties": {
20194
+ "description": {
20195
+ "type": "string"
20196
+ },
20197
+ "quantity": {
20198
+ "type": "number"
20199
+ },
20200
+ "unitAmount": {
20201
+ "type": "number"
20202
+ },
20203
+ "accountCode": {
20204
+ "type": "string",
20205
+ "description": "Account code from list_accounts."
20206
+ },
20207
+ "taxType": {
20208
+ "type": "string",
20209
+ "description": "Tax type from list_tax_rates."
20210
+ }
20211
+ },
20212
+ "required": [
20213
+ "description",
20214
+ "quantity",
20215
+ "unitAmount",
20216
+ "accountCode",
20217
+ "taxType"
20218
+ ],
20219
+ "additionalProperties": false
20220
+ }
20221
+ },
20222
+ "properties": {
20223
+ "tenantId": {
20224
+ "type": "string",
20225
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20226
+ },
20227
+ "contactId": {
20228
+ "type": "string",
20229
+ "description": "Contact ID from list_contacts."
20230
+ },
20231
+ "lineItems": {
20232
+ "type": "array",
20233
+ "items": {
20234
+ "$ref": "#/definitions/lineItem"
20235
+ },
20236
+ "minItems": 1
20237
+ },
20238
+ "type": {
20239
+ "type": "string",
20240
+ "enum": [
20241
+ "ACCRECCREDIT",
20242
+ "ACCPAYCREDIT"
20243
+ ],
20244
+ "description": "ACCRECCREDIT for customer credit notes, ACCPAYCREDIT for supplier credit notes."
20245
+ },
20246
+ "reference": {
20247
+ "type": "string"
20248
+ },
20249
+ "date": {
20250
+ "type": "string",
20251
+ "description": "Credit note date in YYYY-MM-DD format."
20252
+ },
20253
+ "status": {
20254
+ "type": "string",
20255
+ "enum": [
20256
+ "DRAFT",
20257
+ "SUBMITTED",
20258
+ "AUTHORISED"
20259
+ ],
20260
+ "description": "Prefer DRAFT unless the user explicitly asks otherwise."
20261
+ },
20262
+ "extraFields": {
20263
+ "type": "object",
20264
+ "description": "Advanced Xero CreditNote fields to merge into the generated object.",
20265
+ "additionalProperties": true
20266
+ }
20267
+ },
20268
+ "required": [
20269
+ "contactId",
20270
+ "lineItems"
20271
+ ],
20272
+ "additionalProperties": false
20273
+ },
20274
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n })\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeCreditNote = async (note) => {\n const creditNoteId = note?.CreditNoteID || ''\n const shortCode = creditNoteId ? await getShortCode() : ''\n return {\n creditNoteId,\n creditNoteNumber: note?.CreditNoteNumber,\n type: note?.Type,\n status: note?.Status,\n contact: note?.Contact ? { contactId: note.Contact.ContactID, name: note.Contact.Name } : null,\n date: note?.DateString || note?.Date,\n total: note?.Total,\n remainingCredit: note?.RemainingCredit,\n xeroUrl: shortCode && creditNoteId ? `https://go.xero.com/organisationlogin/default.aspx?shortcode=${encodeURIComponent(shortCode)}&redirecturl=/AccountsPayable/ViewCreditNote.aspx?creditNoteID=${encodeURIComponent(creditNoteId)}` : null,\n }\n }\n const creditNote = {\n Type: input.type || 'ACCRECCREDIT',\n Contact: { ContactID: input.contactId },\n LineItems: input.lineItems.map(mapLineItem),\n Date: input.date || today,\n Status: input.status || 'DRAFT',\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/CreditNotes', { CreditNotes: [creditNote] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.CreditNotes) ? data.CreditNotes[0] : null\n return {\n creditNote: created ? await summarizeCreditNote(created) : null,\n }\n}",
20275
+ "scope": "write",
20276
+ "toolset": "accounting"
20277
+ },
20278
+ {
20279
+ "name": "list_quotes",
20280
+ "description": "List quotes with compact quote number, status, contact, dates, and totals.",
20281
+ "inputSchema": {
20282
+ "$schema": "http://json-schema.org/draft-07/schema#",
20283
+ "type": "object",
20284
+ "properties": {
20285
+ "tenantId": {
20286
+ "type": "string",
20287
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20288
+ },
20289
+ "page": {
20290
+ "type": "integer",
20291
+ "minimum": 1,
20292
+ "description": "Xero page number for paginated endpoints."
20293
+ },
20294
+ "where": {
20295
+ "type": "string",
20296
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20297
+ },
20298
+ "order": {
20299
+ "type": "string",
20300
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20301
+ },
20302
+ "modifiedAfter": {
20303
+ "type": "string",
20304
+ "description": "Only include records modified after this RFC3339 timestamp."
20305
+ },
20306
+ "status": {
20307
+ "type": "string",
20308
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20309
+ },
20310
+ "contactIds": {
20311
+ "type": "array",
20312
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20313
+ "items": {
20314
+ "type": "string"
20315
+ }
20316
+ },
20317
+ "invoiceNumbers": {
20318
+ "type": "array",
20319
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20320
+ "items": {
20321
+ "type": "string"
20322
+ }
20323
+ },
20324
+ "fromDate": {
20325
+ "type": "string",
20326
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20327
+ },
20328
+ "toDate": {
20329
+ "type": "string",
20330
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20331
+ },
20332
+ "includeArchived": {
20333
+ "type": "boolean",
20334
+ "description": "Whether to include archived records when the endpoint supports it."
20335
+ }
20336
+ },
20337
+ "additionalProperties": false
20338
+ },
20339
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/Quotes${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const quotes = Array.isArray(data?.Quotes) ? data.Quotes : []\n return {\n quotes: quotes.map(quote => ({\n quoteId: quote.QuoteID,\n quoteNumber: quote.QuoteNumber,\n status: quote.Status,\n contact: quote.Contact ? { contactId: quote.Contact.ContactID, name: quote.Contact.Name } : null,\n date: quote.DateString || quote.Date,\n expiryDate: quote.ExpiryDateString || quote.ExpiryDate,\n total: quote.Total,\n currencyCode: quote.CurrencyCode,\n updatedDateUtc: quote.UpdatedDateUTC,\n })),\n count: quotes.length,\n page: input.page || 1,\n }\n}",
20340
+ "scope": "read",
20341
+ "toolset": "accounting"
20342
+ },
20343
+ {
20344
+ "name": "create_quote",
20345
+ "description": "Create a quote from flat fields. Get contactId from list_contacts, accountCode from list_accounts, and taxType from list_tax_rates. Returns a compact summary and Xero link.",
20346
+ "inputSchema": {
20347
+ "$schema": "http://json-schema.org/draft-07/schema#",
20348
+ "type": "object",
20349
+ "definitions": {
20350
+ "lineItem": {
20351
+ "type": "object",
20352
+ "properties": {
20353
+ "description": {
20354
+ "type": "string"
20355
+ },
20356
+ "quantity": {
20357
+ "type": "number"
20358
+ },
20359
+ "unitAmount": {
20360
+ "type": "number"
20361
+ },
20362
+ "accountCode": {
20363
+ "type": "string",
20364
+ "description": "Account code from list_accounts."
20365
+ },
20366
+ "taxType": {
20367
+ "type": "string",
20368
+ "description": "Tax type from list_tax_rates."
20369
+ },
20370
+ "itemCode": {
20371
+ "type": "string",
20372
+ "description": "Optional item code from list_items."
20373
+ }
20374
+ },
20375
+ "required": [
20376
+ "description",
20377
+ "quantity",
20378
+ "unitAmount",
20379
+ "accountCode",
20380
+ "taxType"
20381
+ ],
20382
+ "additionalProperties": false
20383
+ }
20384
+ },
20385
+ "properties": {
20386
+ "tenantId": {
20387
+ "type": "string",
20388
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20389
+ },
20390
+ "contactId": {
20391
+ "type": "string",
20392
+ "description": "Contact ID from list_contacts."
20393
+ },
20394
+ "lineItems": {
20395
+ "type": "array",
20396
+ "items": {
20397
+ "$ref": "#/definitions/lineItem"
20398
+ },
20399
+ "minItems": 1
20400
+ },
20401
+ "reference": {
20402
+ "type": "string"
20403
+ },
20404
+ "quoteNumber": {
20405
+ "type": "string"
20406
+ },
20407
+ "date": {
20408
+ "type": "string",
20409
+ "description": "Quote date in YYYY-MM-DD format."
20410
+ },
20411
+ "expiryDate": {
20412
+ "type": "string",
20413
+ "description": "Expiry date in YYYY-MM-DD format."
20414
+ },
20415
+ "title": {
20416
+ "type": "string"
20417
+ },
20418
+ "summary": {
20419
+ "type": "string"
20420
+ },
20421
+ "terms": {
20422
+ "type": "string"
20423
+ },
20424
+ "status": {
20425
+ "type": "string",
20426
+ "enum": [
20427
+ "DRAFT",
20428
+ "SENT",
20429
+ "ACCEPTED",
20430
+ "DECLINED"
20431
+ ],
20432
+ "description": "Prefer DRAFT unless the user explicitly asks otherwise."
20433
+ },
20434
+ "extraFields": {
20435
+ "type": "object",
20436
+ "description": "Advanced Xero Quote fields to merge into the generated object.",
20437
+ "additionalProperties": true
20438
+ }
20439
+ },
20440
+ "required": [
20441
+ "contactId",
20442
+ "lineItems"
20443
+ ],
20444
+ "additionalProperties": false
20445
+ },
20446
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n ...(item.itemCode ? { ItemCode: item.itemCode } : {}),\n })\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizeQuote = async (quote) => {\n const quoteId = quote?.QuoteID || ''\n const shortCode = quoteId ? await getShortCode() : ''\n return {\n quoteId,\n quoteNumber: quote?.QuoteNumber,\n status: quote?.Status,\n contact: quote?.Contact ? { contactId: quote.Contact.ContactID, name: quote.Contact.Name } : null,\n date: quote?.DateString || quote?.Date,\n expiryDate: quote?.ExpiryDateString || quote?.ExpiryDate,\n total: quote?.Total,\n xeroUrl: shortCode && quoteId ? `https://go.xero.com/app/${encodeURIComponent(shortCode)}/quotes/view/${encodeURIComponent(quoteId)}` : null,\n }\n }\n const quote = {\n Contact: { ContactID: input.contactId },\n LineItems: input.lineItems.map(mapLineItem),\n Date: input.date || today,\n Status: input.status || 'DRAFT',\n ...(input.expiryDate ? { ExpiryDate: input.expiryDate } : {}),\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.quoteNumber ? { QuoteNumber: input.quoteNumber } : {}),\n ...(input.title ? { Title: input.title } : {}),\n ...(input.summary ? { Summary: input.summary } : {}),\n ...(input.terms ? { Terms: input.terms } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/Quotes', { Quotes: [quote] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.Quotes) ? data.Quotes[0] : null\n return {\n quote: created ? await summarizeQuote(created) : null,\n }\n}",
20447
+ "scope": "write",
20448
+ "toolset": "accounting"
20449
+ },
20450
+ {
20451
+ "name": "list_purchase_orders",
20452
+ "description": "List purchase orders with compact purchase order number, status, contact, delivery date, and totals.",
20453
+ "inputSchema": {
20454
+ "$schema": "http://json-schema.org/draft-07/schema#",
20455
+ "type": "object",
20456
+ "properties": {
20457
+ "tenantId": {
20458
+ "type": "string",
20459
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20460
+ },
20461
+ "page": {
20462
+ "type": "integer",
20463
+ "minimum": 1,
20464
+ "description": "Xero page number for paginated endpoints."
20465
+ },
20466
+ "where": {
20467
+ "type": "string",
20468
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20469
+ },
20470
+ "order": {
20471
+ "type": "string",
20472
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20473
+ },
20474
+ "modifiedAfter": {
20475
+ "type": "string",
20476
+ "description": "Only include records modified after this RFC3339 timestamp."
20477
+ },
20478
+ "status": {
20479
+ "type": "string",
20480
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20481
+ },
20482
+ "contactIds": {
20483
+ "type": "array",
20484
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20485
+ "items": {
20486
+ "type": "string"
20487
+ }
20488
+ },
20489
+ "invoiceNumbers": {
20490
+ "type": "array",
20491
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20492
+ "items": {
20493
+ "type": "string"
20494
+ }
20495
+ },
20496
+ "fromDate": {
20497
+ "type": "string",
20498
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20499
+ },
20500
+ "toDate": {
20501
+ "type": "string",
20502
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20503
+ },
20504
+ "includeArchived": {
20505
+ "type": "boolean",
20506
+ "description": "Whether to include archived records when the endpoint supports it."
20507
+ }
20508
+ },
20509
+ "additionalProperties": false
20510
+ },
20511
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/PurchaseOrders${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const purchaseOrders = Array.isArray(data?.PurchaseOrders) ? data.PurchaseOrders : []\n return {\n purchaseOrders: purchaseOrders.map(po => ({\n purchaseOrderId: po.PurchaseOrderID,\n purchaseOrderNumber: po.PurchaseOrderNumber,\n status: po.Status,\n contact: po.Contact ? { contactId: po.Contact.ContactID, name: po.Contact.Name } : null,\n date: po.DateString || po.Date,\n deliveryDate: po.DeliveryDateString || po.DeliveryDate,\n total: po.Total,\n currencyCode: po.CurrencyCode,\n updatedDateUtc: po.UpdatedDateUTC,\n })),\n count: purchaseOrders.length,\n page: input.page || 1,\n }\n}",
20512
+ "scope": "read",
20513
+ "toolset": "accounting"
20514
+ },
20515
+ {
20516
+ "name": "create_purchase_order",
20517
+ "description": "Create a purchase order from flat fields. Get supplier contactId from list_contacts and line account/tax values from list_accounts/list_tax_rates.",
20518
+ "inputSchema": {
20519
+ "$schema": "http://json-schema.org/draft-07/schema#",
20520
+ "type": "object",
20521
+ "definitions": {
20522
+ "lineItem": {
20523
+ "type": "object",
20524
+ "properties": {
20525
+ "description": {
20526
+ "type": "string"
20527
+ },
20528
+ "quantity": {
20529
+ "type": "number"
20530
+ },
20531
+ "unitAmount": {
20532
+ "type": "number"
20533
+ },
20534
+ "accountCode": {
20535
+ "type": "string",
20536
+ "description": "Account code from list_accounts."
20537
+ },
20538
+ "taxType": {
20539
+ "type": "string",
20540
+ "description": "Tax type from list_tax_rates."
20541
+ },
20542
+ "itemCode": {
20543
+ "type": "string",
20544
+ "description": "Optional item code from list_items."
20545
+ }
20546
+ },
20547
+ "required": [
20548
+ "description",
20549
+ "quantity",
20550
+ "unitAmount",
20551
+ "accountCode",
20552
+ "taxType"
20553
+ ],
20554
+ "additionalProperties": false
20555
+ }
20556
+ },
20557
+ "properties": {
20558
+ "tenantId": {
20559
+ "type": "string",
20560
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20561
+ },
20562
+ "contactId": {
20563
+ "type": "string",
20564
+ "description": "Supplier contact ID from list_contacts."
20565
+ },
20566
+ "lineItems": {
20567
+ "type": "array",
20568
+ "items": {
20569
+ "$ref": "#/definitions/lineItem"
20570
+ },
20571
+ "minItems": 1
20572
+ },
20573
+ "date": {
20574
+ "type": "string",
20575
+ "description": "Purchase order date in YYYY-MM-DD format."
20576
+ },
20577
+ "deliveryDate": {
20578
+ "type": "string",
20579
+ "description": "Delivery date in YYYY-MM-DD format."
20580
+ },
20581
+ "reference": {
20582
+ "type": "string"
20583
+ },
20584
+ "status": {
20585
+ "type": "string",
20586
+ "enum": [
20587
+ "DRAFT",
20588
+ "SUBMITTED",
20589
+ "AUTHORISED",
20590
+ "BILLED"
20591
+ ],
20592
+ "description": "Prefer DRAFT unless the user explicitly asks otherwise."
20593
+ },
20594
+ "extraFields": {
20595
+ "type": "object",
20596
+ "description": "Advanced Xero PurchaseOrder fields to merge into the generated object.",
20597
+ "additionalProperties": true
20598
+ }
20599
+ },
20600
+ "required": [
20601
+ "contactId",
20602
+ "lineItems"
20603
+ ],
20604
+ "additionalProperties": false
20605
+ },
20606
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n ...(item.itemCode ? { ItemCode: item.itemCode } : {}),\n })\n const summarizePurchaseOrder = po => ({\n purchaseOrderId: po?.PurchaseOrderID,\n purchaseOrderNumber: po?.PurchaseOrderNumber,\n status: po?.Status,\n contact: po?.Contact ? { contactId: po.Contact.ContactID, name: po.Contact.Name } : null,\n date: po?.DateString || po?.Date,\n deliveryDate: po?.DeliveryDateString || po?.DeliveryDate,\n total: po?.Total,\n lineItemCount: Array.isArray(po?.LineItems) ? po.LineItems.length : undefined,\n })\n const purchaseOrder = {\n Contact: { ContactID: input.contactId },\n LineItems: input.lineItems.map(mapLineItem),\n Date: input.date || today,\n Status: input.status || 'DRAFT',\n ...(input.deliveryDate ? { DeliveryDate: input.deliveryDate } : {}),\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/PurchaseOrders', { PurchaseOrders: [purchaseOrder] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.PurchaseOrders) ? data.PurchaseOrders[0] : null\n return {\n purchaseOrder: created ? summarizePurchaseOrder(created) : null,\n }\n}",
20607
+ "scope": "write",
20608
+ "toolset": "accounting"
20609
+ },
20610
+ {
20611
+ "name": "list_payments",
20612
+ "description": "List payments with compact payment ID, date, amount, status, account, and invoice references.",
20613
+ "inputSchema": {
20614
+ "$schema": "http://json-schema.org/draft-07/schema#",
20615
+ "type": "object",
20616
+ "properties": {
20617
+ "tenantId": {
20618
+ "type": "string",
20619
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20620
+ },
20621
+ "page": {
20622
+ "type": "integer",
20623
+ "minimum": 1,
20624
+ "description": "Xero page number for paginated endpoints."
20625
+ },
20626
+ "where": {
20627
+ "type": "string",
20628
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20629
+ },
20630
+ "order": {
20631
+ "type": "string",
20632
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20633
+ },
20634
+ "modifiedAfter": {
20635
+ "type": "string",
20636
+ "description": "Only include records modified after this RFC3339 timestamp."
20637
+ },
20638
+ "status": {
20639
+ "type": "string",
20640
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20641
+ },
20642
+ "contactIds": {
20643
+ "type": "array",
20644
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20645
+ "items": {
20646
+ "type": "string"
20647
+ }
20648
+ },
20649
+ "invoiceNumbers": {
20650
+ "type": "array",
20651
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20652
+ "items": {
20653
+ "type": "string"
20654
+ }
20655
+ },
20656
+ "fromDate": {
20657
+ "type": "string",
20658
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20659
+ },
20660
+ "toDate": {
20661
+ "type": "string",
20662
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20663
+ },
20664
+ "includeArchived": {
20665
+ "type": "boolean",
20666
+ "description": "Whether to include archived records when the endpoint supports it."
20667
+ }
20668
+ },
20669
+ "additionalProperties": false
20670
+ },
20671
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.fromDate || input.toDate) {\n const clauses = []\n if (input.fromDate) clauses.push(`Date >= DateTime(${input.fromDate.replace(/-/g, ',')})`)\n if (input.toDate) clauses.push(`Date <= DateTime(${input.toDate.replace(/-/g, ',')})`)\n params.set('where', input.where ? `${input.where}&&${clauses.join('&&')}` : clauses.join('&&'))\n }\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/Payments${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const payments = Array.isArray(data?.Payments) ? data.Payments : []\n return {\n payments: payments.map(payment => ({\n paymentId: payment.PaymentID,\n status: payment.Status,\n date: payment.DateString || payment.Date,\n amount: payment.Amount,\n currencyRate: payment.CurrencyRate,\n account: payment.Account ? { accountId: payment.Account.AccountID, code: payment.Account.Code, name: payment.Account.Name } : null,\n invoice: payment.Invoice ? { invoiceId: payment.Invoice.InvoiceID, invoiceNumber: payment.Invoice.InvoiceNumber } : null,\n updatedDateUtc: payment.UpdatedDateUTC,\n })),\n count: payments.length,\n page: input.page || 1,\n }\n}",
20672
+ "scope": "read",
20673
+ "toolset": "accounting"
20674
+ },
20675
+ {
20676
+ "name": "create_payment",
20677
+ "description": "Create a payment against an invoice. Use get_invoice for invoiceId/amount due and list_accounts for the payment accountId. Returns a compact summary and Xero link.",
20678
+ "inputSchema": {
20679
+ "$schema": "http://json-schema.org/draft-07/schema#",
20680
+ "type": "object",
20681
+ "properties": {
20682
+ "tenantId": {
20683
+ "type": "string",
20684
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20685
+ },
20686
+ "invoiceId": {
20687
+ "type": "string",
20688
+ "description": "Invoice ID from list_invoices or get_invoice."
20689
+ },
20690
+ "accountId": {
20691
+ "type": "string",
20692
+ "description": "Payment account ID from list_accounts."
20693
+ },
20694
+ "amount": {
20695
+ "type": "number",
20696
+ "description": "Payment amount."
20697
+ },
20698
+ "date": {
20699
+ "type": "string",
20700
+ "description": "Payment date in YYYY-MM-DD format. Defaults to today when omitted."
20701
+ },
20702
+ "reference": {
20703
+ "type": "string",
20704
+ "description": "Optional payment reference."
20705
+ },
20706
+ "extraFields": {
20707
+ "type": "object",
20708
+ "description": "Advanced Xero Payment fields to merge into the generated payment object.",
20709
+ "additionalProperties": true
20710
+ }
20711
+ },
20712
+ "required": [
20713
+ "invoiceId",
20714
+ "accountId",
20715
+ "amount"
20716
+ ],
20717
+ "additionalProperties": false
20718
+ },
20719
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const getShortCode = async () => {\n try {\n const orgRes = await integration.get('/api.xro/2.0/Organisation', { headers })\n const orgData = await orgRes.json()\n return Array.isArray(orgData?.Organisations) ? orgData.Organisations[0]?.ShortCode : ''\n }\n catch {\n return ''\n }\n }\n const summarizePayment = async (payment) => {\n const paymentId = payment?.PaymentID || ''\n const shortCode = paymentId ? await getShortCode() : ''\n return {\n paymentId,\n status: payment?.Status,\n date: payment?.DateString || payment?.Date,\n amount: payment?.Amount,\n reference: payment?.Reference,\n account: payment?.Account ? { accountId: payment.Account.AccountID, code: payment.Account.Code, name: payment.Account.Name } : null,\n invoice: payment?.Invoice ? { invoiceId: payment.Invoice.InvoiceID, invoiceNumber: payment.Invoice.InvoiceNumber } : null,\n xeroUrl: shortCode && paymentId ? `https://go.xero.com/organisationlogin/default.aspx?shortcode=${encodeURIComponent(shortCode)}&redirecturl=/Bank/ViewTransaction.aspx?bankTransactionID=${encodeURIComponent(paymentId)}` : null,\n }\n }\n const payment = {\n Invoice: { InvoiceID: input.invoiceId },\n Account: { AccountID: input.accountId },\n Amount: input.amount,\n Date: input.date || today,\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/Payments', { Payments: [payment] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.Payments) ? data.Payments[0] : null\n return {\n payment: created ? await summarizePayment(created) : null,\n }\n}",
20720
+ "scope": "write",
20721
+ "toolset": "accounting"
20722
+ },
20723
+ {
20724
+ "name": "list_bank_transactions",
20725
+ "description": "List bank transactions with compact type, status, contact, date, total, and bank account details.",
20726
+ "inputSchema": {
20727
+ "$schema": "http://json-schema.org/draft-07/schema#",
20728
+ "type": "object",
20729
+ "properties": {
20730
+ "tenantId": {
20731
+ "type": "string",
20732
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20733
+ },
20734
+ "page": {
20735
+ "type": "integer",
20736
+ "minimum": 1,
20737
+ "description": "Xero page number for paginated endpoints."
20738
+ },
20739
+ "where": {
20740
+ "type": "string",
20741
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20742
+ },
20743
+ "order": {
20744
+ "type": "string",
20745
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20746
+ },
20747
+ "modifiedAfter": {
20748
+ "type": "string",
20749
+ "description": "Only include records modified after this RFC3339 timestamp."
20750
+ },
20751
+ "status": {
20752
+ "type": "string",
20753
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20754
+ },
20755
+ "contactIds": {
20756
+ "type": "array",
20757
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20758
+ "items": {
20759
+ "type": "string"
20760
+ }
20761
+ },
20762
+ "invoiceNumbers": {
20763
+ "type": "array",
20764
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20765
+ "items": {
20766
+ "type": "string"
20767
+ }
20768
+ },
20769
+ "fromDate": {
20770
+ "type": "string",
20771
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20772
+ },
20773
+ "toDate": {
20774
+ "type": "string",
20775
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20776
+ },
20777
+ "includeArchived": {
20778
+ "type": "boolean",
20779
+ "description": "Whether to include archived records when the endpoint supports it."
20780
+ }
20781
+ },
20782
+ "additionalProperties": false
20783
+ },
20784
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/BankTransactions${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const bankTransactions = Array.isArray(data?.BankTransactions) ? data.BankTransactions : []\n return {\n bankTransactions: bankTransactions.map(txn => ({\n bankTransactionId: txn.BankTransactionID,\n type: txn.Type,\n status: txn.Status,\n contact: txn.Contact ? { contactId: txn.Contact.ContactID, name: txn.Contact.Name } : null,\n bankAccount: txn.BankAccount ? { accountId: txn.BankAccount.AccountID, code: txn.BankAccount.Code, name: txn.BankAccount.Name } : null,\n date: txn.DateString || txn.Date,\n total: txn.Total,\n currencyCode: txn.CurrencyCode,\n updatedDateUtc: txn.UpdatedDateUTC,\n })),\n count: bankTransactions.length,\n page: input.page || 1,\n }\n}",
20785
+ "scope": "read",
20786
+ "toolset": "accounting"
20787
+ },
20788
+ {
20789
+ "name": "create_bank_transaction",
20790
+ "description": "Create a spend or receive bank transaction. Use list_accounts for valid bank/account codes and tax rates before calling this.",
20791
+ "inputSchema": {
20792
+ "$schema": "http://json-schema.org/draft-07/schema#",
20793
+ "type": "object",
20794
+ "definitions": {
20795
+ "lineItem": {
20796
+ "type": "object",
20797
+ "properties": {
20798
+ "description": {
20799
+ "type": "string",
20800
+ "description": "Line item description."
20801
+ },
20802
+ "quantity": {
20803
+ "type": "number"
20804
+ },
20805
+ "unitAmount": {
20806
+ "type": "number"
20807
+ },
20808
+ "accountCode": {
20809
+ "type": "string",
20810
+ "description": "Account code from list_accounts."
20811
+ },
20812
+ "taxType": {
20813
+ "type": "string",
20814
+ "description": "Tax type from list_tax_rates."
20815
+ }
20816
+ },
20817
+ "required": [
20818
+ "description",
20819
+ "quantity",
20820
+ "unitAmount",
20821
+ "accountCode",
20822
+ "taxType"
20823
+ ],
20824
+ "additionalProperties": false
20825
+ }
20826
+ },
20827
+ "properties": {
20828
+ "tenantId": {
20829
+ "type": "string",
20830
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20831
+ },
20832
+ "type": {
20833
+ "type": "string",
20834
+ "enum": [
20835
+ "RECEIVE",
20836
+ "SPEND"
20837
+ ],
20838
+ "description": "RECEIVE for money received, SPEND for money spent."
20839
+ },
20840
+ "bankAccountId": {
20841
+ "type": "string",
20842
+ "description": "Bank account ID from list_accounts."
20843
+ },
20844
+ "contactId": {
20845
+ "type": "string",
20846
+ "description": "Contact ID from list_contacts."
20847
+ },
20848
+ "lineItems": {
20849
+ "type": "array",
20850
+ "description": "Bank transaction line items.",
20851
+ "items": {
20852
+ "$ref": "#/definitions/lineItem"
20853
+ },
20854
+ "minItems": 1
20855
+ },
20856
+ "reference": {
20857
+ "type": "string"
20858
+ },
20859
+ "date": {
20860
+ "type": "string",
20861
+ "description": "Transaction date in YYYY-MM-DD format."
20862
+ },
20863
+ "status": {
20864
+ "type": "string",
20865
+ "enum": [
20866
+ "DRAFT",
20867
+ "AUTHORISED"
20868
+ ],
20869
+ "description": "Prefer DRAFT unless the user explicitly asks to authorise."
20870
+ },
20871
+ "extraFields": {
20872
+ "type": "object",
20873
+ "description": "Advanced Xero BankTransaction fields to merge into the generated object.",
20874
+ "additionalProperties": true
20875
+ }
20876
+ },
20877
+ "required": [
20878
+ "type",
20879
+ "bankAccountId",
20880
+ "contactId",
20881
+ "lineItems"
20882
+ ],
20883
+ "additionalProperties": false
20884
+ },
20885
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const mapLineItem = item => ({\n Description: item.description,\n Quantity: item.quantity,\n UnitAmount: item.unitAmount,\n AccountCode: item.accountCode,\n TaxType: item.taxType,\n })\n const summarizeBankTransaction = txn => ({\n bankTransactionId: txn?.BankTransactionID,\n type: txn?.Type,\n status: txn?.Status,\n contact: txn?.Contact ? { contactId: txn.Contact.ContactID, name: txn.Contact.Name } : null,\n bankAccount: txn?.BankAccount ? { accountId: txn.BankAccount.AccountID, code: txn.BankAccount.Code, name: txn.BankAccount.Name } : null,\n date: txn?.DateString || txn?.Date,\n reference: txn?.Reference,\n total: txn?.Total,\n lineItemCount: Array.isArray(txn?.LineItems) ? txn.LineItems.length : undefined,\n xeroUrl: txn?.BankAccount?.AccountID && txn?.BankTransactionID\n ? `https://go.xero.com/Bank/ViewTransaction.aspx?bankTransactionID=${encodeURIComponent(txn.BankTransactionID)}&accountID=${encodeURIComponent(txn.BankAccount.AccountID)}`\n : null,\n })\n const bankTransaction = {\n Type: input.type,\n BankAccount: { AccountID: input.bankAccountId },\n Contact: { ContactID: input.contactId },\n LineItems: input.lineItems.map(mapLineItem),\n Date: input.date || today,\n Status: input.status || 'DRAFT',\n ...(input.reference ? { Reference: input.reference } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/BankTransactions', { BankTransactions: [bankTransaction] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.BankTransactions) ? data.BankTransactions[0] : null\n return {\n bankTransaction: created ? summarizeBankTransaction(created) : null,\n }\n}",
20886
+ "scope": "write",
20887
+ "toolset": "accounting"
20888
+ },
20889
+ {
20890
+ "name": "list_manual_journals",
20891
+ "description": "List manual journals with compact status, narration, date, journal ID, and line count.",
20892
+ "inputSchema": {
20893
+ "$schema": "http://json-schema.org/draft-07/schema#",
20894
+ "type": "object",
20895
+ "properties": {
20896
+ "tenantId": {
20897
+ "type": "string",
20898
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20899
+ },
20900
+ "page": {
20901
+ "type": "integer",
20902
+ "minimum": 1,
20903
+ "description": "Xero page number for paginated endpoints."
20904
+ },
20905
+ "where": {
20906
+ "type": "string",
20907
+ "description": 'Advanced Xero where filter expression, e.g. Status=="AUTHORISED". Prefer the explicit filter fields when available.'
20908
+ },
20909
+ "order": {
20910
+ "type": "string",
20911
+ "description": "Xero order expression, e.g. UpdatedDateUTC DESC."
20912
+ },
20913
+ "modifiedAfter": {
20914
+ "type": "string",
20915
+ "description": "Only include records modified after this RFC3339 timestamp."
20916
+ },
20917
+ "status": {
20918
+ "type": "string",
20919
+ "description": "Convenience status filter for endpoints that support a Status query parameter."
20920
+ },
20921
+ "contactIds": {
20922
+ "type": "array",
20923
+ "description": "Filter invoices/transactions to one or more contact IDs from list_contacts.",
20924
+ "items": {
20925
+ "type": "string"
20926
+ }
20927
+ },
20928
+ "invoiceNumbers": {
20929
+ "type": "array",
20930
+ "description": "Filter invoices by invoice number. For invoices, line items are returned by Xero when invoice numbers are supplied.",
20931
+ "items": {
20932
+ "type": "string"
20933
+ }
20934
+ },
20935
+ "fromDate": {
20936
+ "type": "string",
20937
+ "description": "Convenience start date filter in YYYY-MM-DD format for date-based resources."
20938
+ },
20939
+ "toDate": {
20940
+ "type": "string",
20941
+ "description": "Convenience end date filter in YYYY-MM-DD format for date-based resources."
20942
+ },
20943
+ "includeArchived": {
20944
+ "type": "boolean",
20945
+ "description": "Whether to include archived records when the endpoint supports it."
20946
+ }
20947
+ },
20948
+ "additionalProperties": false
20949
+ },
20950
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.page) params.set('page', String(input.page))\n if (input.where) params.set('where', input.where)\n if (input.order) params.set('order', input.order)\n if (input.status) params.set('Statuses', input.status)\n if (input.modifiedAfter) params.set('If-Modified-Since', input.modifiedAfter)\n const res = await integration.get(`/api.xro/2.0/ManualJournals${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const manualJournals = Array.isArray(data?.ManualJournals) ? data.ManualJournals : []\n return {\n manualJournals: manualJournals.map(journal => ({\n manualJournalId: journal.ManualJournalID,\n narration: journal.Narration,\n status: journal.Status,\n date: journal.DateString || journal.Date,\n lineAmountTypes: journal.LineAmountTypes,\n journalLineCount: Array.isArray(journal.JournalLines) ? journal.JournalLines.length : undefined,\n updatedDateUtc: journal.UpdatedDateUTC,\n })),\n count: manualJournals.length,\n page: input.page || 1,\n }\n}",
20951
+ "scope": "read",
20952
+ "toolset": "accounting"
20953
+ },
20954
+ {
20955
+ "name": "create_manual_journal",
20956
+ "description": "Create a manual journal. Use this carefully; discover account codes with list_accounts and prefer DRAFT when supported by the organisation workflow.",
20957
+ "inputSchema": {
20958
+ "$schema": "http://json-schema.org/draft-07/schema#",
20959
+ "type": "object",
20960
+ "definitions": {
20961
+ "journalLine": {
20962
+ "type": "object",
20963
+ "properties": {
20964
+ "lineAmount": {
20965
+ "type": "number",
20966
+ "description": "Debit lines are positive; credit lines are negative."
20967
+ },
20968
+ "accountCode": {
20969
+ "type": "string",
20970
+ "description": "Account code from list_accounts."
20971
+ },
20972
+ "description": {
20973
+ "type": "string"
20974
+ },
20975
+ "taxType": {
20976
+ "type": "string",
20977
+ "description": "Optional tax type from list_tax_rates."
20978
+ }
20979
+ },
20980
+ "required": [
20981
+ "lineAmount",
20982
+ "accountCode"
20983
+ ],
20984
+ "additionalProperties": false
20985
+ }
20986
+ },
20987
+ "properties": {
20988
+ "tenantId": {
20989
+ "type": "string",
20990
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
20991
+ },
20992
+ "narration": {
20993
+ "type": "string",
20994
+ "description": "Description of the journal."
20995
+ },
20996
+ "journalLines": {
20997
+ "type": "array",
20998
+ "description": "At least two balanced journal lines. Debits must equal credits.",
20999
+ "items": {
21000
+ "$ref": "#/definitions/journalLine"
21001
+ },
21002
+ "minItems": 2
21003
+ },
21004
+ "date": {
21005
+ "type": "string",
21006
+ "description": "Journal date in YYYY-MM-DD format."
21007
+ },
21008
+ "lineAmountTypes": {
21009
+ "type": "string",
21010
+ "enum": [
21011
+ "EXCLUSIVE",
21012
+ "INCLUSIVE",
21013
+ "NO_TAX"
21014
+ ],
21015
+ "description": "Defaults to NO_TAX."
21016
+ },
21017
+ "status": {
21018
+ "type": "string",
21019
+ "enum": [
21020
+ "DRAFT",
21021
+ "POSTED"
21022
+ ],
21023
+ "description": "Prefer DRAFT unless the user explicitly asks to post."
21024
+ },
21025
+ "url": {
21026
+ "type": "string",
21027
+ "description": "Optional URL to a source document."
21028
+ },
21029
+ "showOnCashBasisReports": {
21030
+ "type": "boolean"
21031
+ },
21032
+ "extraFields": {
21033
+ "type": "object",
21034
+ "description": "Advanced Xero ManualJournal fields to merge into the generated object.",
21035
+ "additionalProperties": true
21036
+ }
21037
+ },
21038
+ "required": [
21039
+ "narration",
21040
+ "journalLines"
21041
+ ],
21042
+ "additionalProperties": false
21043
+ },
21044
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const today = new Date().toISOString().slice(0, 10)\n const mapJournalLine = line => ({\n LineAmount: line.lineAmount,\n AccountCode: line.accountCode,\n ...(line.description ? { Description: line.description } : {}),\n ...(line.taxType ? { TaxType: line.taxType } : {}),\n })\n const summarizeManualJournal = journal => ({\n manualJournalId: journal?.ManualJournalID,\n narration: journal?.Narration,\n status: journal?.Status,\n date: journal?.DateString || journal?.Date,\n lineAmountTypes: journal?.LineAmountTypes,\n showOnCashBasisReports: journal?.ShowOnCashBasisReports,\n journalLineCount: Array.isArray(journal?.JournalLines) ? journal.JournalLines.length : undefined,\n xeroUrl: journal?.ManualJournalID ? `https://go.xero.com/Journal/View.aspx?invoiceID=${encodeURIComponent(journal.ManualJournalID)}` : null,\n })\n const lineAmountTypeMap = {\n EXCLUSIVE: 'Exclusive',\n INCLUSIVE: 'Inclusive',\n NO_TAX: 'NoTax',\n }\n const manualJournal = {\n Narration: input.narration,\n JournalLines: input.journalLines.map(mapJournalLine),\n Date: input.date || today,\n LineAmountTypes: lineAmountTypeMap[input.lineAmountTypes || 'NO_TAX'] || input.lineAmountTypes,\n Status: input.status || 'DRAFT',\n ...(input.url ? { Url: input.url } : {}),\n ...(input.showOnCashBasisReports !== undefined ? { ShowOnCashBasisReports: input.showOnCashBasisReports } : {}),\n ...(input.extraFields || {}),\n }\n const res = await integration.post('/api.xro/2.0/ManualJournals', { ManualJournals: [manualJournal] }, { headers })\n const data = await res.json()\n const created = Array.isArray(data?.ManualJournals) ? data.ManualJournals[0] : null\n return {\n manualJournal: created ? summarizeManualJournal(created) : null,\n }\n}",
21045
+ "scope": "write",
21046
+ "toolset": "accounting"
21047
+ },
21048
+ {
21049
+ "name": "list_attachments",
21050
+ "description": "List attachments for a supported Xero resource such as Invoices, Contacts, BankTransactions, CreditNotes, PurchaseOrders, or ManualJournals.",
21051
+ "inputSchema": {
21052
+ "$schema": "http://json-schema.org/draft-07/schema#",
21053
+ "type": "object",
21054
+ "properties": {
21055
+ "tenantId": {
21056
+ "type": "string",
21057
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21058
+ },
21059
+ "resourceType": {
21060
+ "type": "string",
21061
+ "enum": [
21062
+ "Accounts",
21063
+ "BankTransactions",
21064
+ "BankTransfers",
21065
+ "Contacts",
21066
+ "CreditNotes",
21067
+ "Invoices",
21068
+ "ManualJournals",
21069
+ "PurchaseOrders",
21070
+ "Receipts",
21071
+ "RepeatingInvoices"
21072
+ ],
21073
+ "description": "Xero attachment parent endpoint."
21074
+ },
21075
+ "resourceId": {
21076
+ "type": "string",
21077
+ "description": "ID of the parent Xero resource."
21078
+ }
21079
+ },
21080
+ "required": [
21081
+ "resourceType",
21082
+ "resourceId"
21083
+ ],
21084
+ "additionalProperties": false
21085
+ },
21086
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const path = `/api.xro/2.0/${encodeURIComponent(input.resourceType)}/${encodeURIComponent(input.resourceId)}/Attachments`\n const res = await integration.get(path, { headers })\n const data = await res.json()\n const attachments = Array.isArray(data?.Attachments) ? data.Attachments : []\n return {\n attachments: attachments.map(attachment => ({\n attachmentId: attachment.AttachmentID,\n fileName: attachment.FileName,\n mimeType: attachment.MimeType,\n contentLength: attachment.ContentLength,\n includeOnline: attachment.IncludeOnline,\n url: attachment.Url,\n })),\n count: attachments.length,\n }\n}",
21087
+ "scope": "read",
21088
+ "toolset": "accounting"
21089
+ },
21090
+ {
21091
+ "name": "read_attachment_content",
21092
+ "description": "Extract readable text from an attachment on a supported Xero resource. Uses the shared file extractor for PDFs, Office files, CSV, text, HTML, and similar formats.",
21093
+ "inputSchema": {
21094
+ "$schema": "http://json-schema.org/draft-07/schema#",
21095
+ "type": "object",
21096
+ "properties": {
21097
+ "tenantId": {
21098
+ "type": "string",
21099
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21100
+ },
21101
+ "resourceType": {
21102
+ "type": "string",
21103
+ "enum": [
21104
+ "Accounts",
21105
+ "BankTransactions",
21106
+ "BankTransfers",
21107
+ "Contacts",
21108
+ "CreditNotes",
21109
+ "Invoices",
21110
+ "ManualJournals",
21111
+ "PurchaseOrders",
21112
+ "Receipts",
21113
+ "RepeatingInvoices"
21114
+ ],
21115
+ "description": "Xero attachment parent endpoint."
21116
+ },
21117
+ "resourceId": {
21118
+ "type": "string",
21119
+ "description": "ID of the parent Xero resource."
21120
+ },
21121
+ "fileName": {
21122
+ "type": "string",
21123
+ "description": "Attachment file name from list_attachments."
21124
+ }
21125
+ },
21126
+ "required": [
21127
+ "resourceType",
21128
+ "resourceId",
21129
+ "fileName"
21130
+ ],
21131
+ "additionalProperties": false
21132
+ },
21133
+ "handlerCode": "async (input) => {\n const path = `/api.xro/2.0/${encodeURIComponent(input.resourceType)}/${encodeURIComponent(input.resourceId)}/Attachments/${encodeURIComponent(input.fileName)}`\n if (input.tenantId)\n throw new Error('read_attachment_content currently supports Custom Connections only because the shared file extractor cannot pass xero-tenant-id yet.')\n const extracted = await utils.extractFileContent({\n auth: true,\n source: path,\n })\n return {\n fileName: input.fileName,\n resourceType: input.resourceType,\n resourceId: input.resourceId,\n ...extracted,\n }\n}",
21134
+ "scope": "read",
21135
+ "toolset": "accounting"
21136
+ },
21137
+ {
21138
+ "name": "get_profit_and_loss",
21139
+ "description": "Get the Profit and Loss report for a date range or period. Returns Xero's report rows plus helper metadata for the query used.",
21140
+ "inputSchema": {
21141
+ "$schema": "http://json-schema.org/draft-07/schema#",
21142
+ "type": "object",
21143
+ "properties": {
21144
+ "tenantId": {
21145
+ "type": "string",
21146
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21147
+ },
21148
+ "fromDate": {
21149
+ "type": "string",
21150
+ "description": "Start date in YYYY-MM-DD format."
21151
+ },
21152
+ "toDate": {
21153
+ "type": "string",
21154
+ "description": "End date in YYYY-MM-DD format."
21155
+ },
21156
+ "periods": {
21157
+ "type": "integer",
21158
+ "minimum": 1,
21159
+ "description": "Number of comparison periods."
21160
+ },
21161
+ "timeframe": {
21162
+ "type": "string",
21163
+ "enum": [
21164
+ "MONTH",
21165
+ "QUARTER",
21166
+ "YEAR"
21167
+ ],
21168
+ "description": "Comparison period timeframe."
21169
+ },
21170
+ "trackingCategoryId": {
21171
+ "type": "string"
21172
+ },
21173
+ "trackingOptionId": {
21174
+ "type": "string"
21175
+ },
21176
+ "standardLayout": {
21177
+ "type": "boolean",
21178
+ "description": "Use Xero's standard Profit and Loss layout when true."
21179
+ },
21180
+ "paymentsOnly": {
21181
+ "type": "boolean",
21182
+ "description": "Limit the report to payments-only values when supported by the organisation."
21183
+ }
21184
+ },
21185
+ "additionalProperties": false
21186
+ },
21187
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.fromDate) params.set('fromDate', input.fromDate)\n if (input.toDate) params.set('toDate', input.toDate)\n if (input.periods) params.set('periods', String(input.periods))\n if (input.timeframe) params.set('timeframe', input.timeframe)\n if (input.trackingCategoryId) params.set('trackingCategoryID', input.trackingCategoryId)\n if (input.trackingOptionId) params.set('trackingOptionID', input.trackingOptionId)\n if (input.standardLayout !== undefined) params.set('standardLayout', String(input.standardLayout))\n if (input.paymentsOnly !== undefined) params.set('paymentsOnly', String(input.paymentsOnly))\n const res = await integration.get(`/api.xro/2.0/Reports/ProfitAndLoss${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const headings = rows.find(row => row.rowType === 'Header')?.cells || []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { fromDate: input.fromDate || null, toDate: input.toDate || null },\n updatedDateUtc: report.UpdatedDateUTC,\n headings,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21188
+ "scope": "read",
21189
+ "toolset": "reports"
21190
+ },
21191
+ {
21192
+ "name": "get_balance_sheet",
21193
+ "description": "Get the Balance Sheet report for a date or period. Use this for financial position summaries.",
21194
+ "inputSchema": {
21195
+ "$schema": "http://json-schema.org/draft-07/schema#",
21196
+ "type": "object",
21197
+ "properties": {
21198
+ "tenantId": {
21199
+ "type": "string",
21200
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21201
+ },
21202
+ "date": {
21203
+ "type": "string",
21204
+ "description": "Report date in YYYY-MM-DD format."
21205
+ },
21206
+ "periods": {
21207
+ "type": "integer",
21208
+ "minimum": 1,
21209
+ "description": "Number of comparison periods."
21210
+ },
21211
+ "timeframe": {
21212
+ "type": "string",
21213
+ "enum": [
21214
+ "MONTH",
21215
+ "QUARTER",
21216
+ "YEAR"
21217
+ ],
21218
+ "description": "Comparison period timeframe."
21219
+ },
21220
+ "trackingCategoryId": {
21221
+ "type": "string"
21222
+ },
21223
+ "trackingOptionId": {
21224
+ "type": "string"
21225
+ }
21226
+ },
21227
+ "additionalProperties": false
21228
+ },
21229
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.date) params.set('date', input.date)\n if (input.periods) params.set('periods', String(input.periods))\n if (input.timeframe) params.set('timeframe', input.timeframe)\n if (input.trackingCategoryId) params.set('trackingCategoryID', input.trackingCategoryId)\n if (input.trackingOptionId) params.set('trackingOptionID', input.trackingOptionId)\n const res = await integration.get(`/api.xro/2.0/Reports/BalanceSheet${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const headings = rows.find(row => row.rowType === 'Header')?.cells || []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { date: input.date || null },\n updatedDateUtc: report.UpdatedDateUTC,\n headings,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21230
+ "scope": "read",
21231
+ "toolset": "reports"
21232
+ },
21233
+ {
21234
+ "name": "get_trial_balance",
21235
+ "description": "Get the Trial Balance report at a point in time.",
21236
+ "inputSchema": {
21237
+ "$schema": "http://json-schema.org/draft-07/schema#",
21238
+ "type": "object",
21239
+ "properties": {
21240
+ "tenantId": {
21241
+ "type": "string",
21242
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21243
+ },
21244
+ "date": {
21245
+ "type": "string",
21246
+ "description": "Report date in YYYY-MM-DD format."
21247
+ }
21248
+ },
21249
+ "additionalProperties": false
21250
+ },
21251
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.date) params.set('date', input.date)\n const res = await integration.get(`/api.xro/2.0/Reports/TrialBalance${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const headings = rows.find(row => row.rowType === 'Header')?.cells || []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { date: input.date || null },\n updatedDateUtc: report.UpdatedDateUTC,\n headings,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21252
+ "scope": "read",
21253
+ "toolset": "reports"
21254
+ },
21255
+ {
21256
+ "name": "get_bank_summary",
21257
+ "description": "Get the Bank Summary report for a date range.",
21258
+ "inputSchema": {
21259
+ "$schema": "http://json-schema.org/draft-07/schema#",
21260
+ "type": "object",
21261
+ "properties": {
21262
+ "tenantId": {
21263
+ "type": "string",
21264
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21265
+ },
21266
+ "fromDate": {
21267
+ "type": "string",
21268
+ "description": "Start date in YYYY-MM-DD format."
21269
+ },
21270
+ "toDate": {
21271
+ "type": "string",
21272
+ "description": "End date in YYYY-MM-DD format."
21273
+ }
21274
+ },
21275
+ "additionalProperties": false
21276
+ },
21277
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.fromDate) params.set('fromDate', input.fromDate)\n if (input.toDate) params.set('toDate', input.toDate)\n const res = await integration.get(`/api.xro/2.0/Reports/BankSummary${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const headings = rows.find(row => row.rowType === 'Header')?.cells || []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { fromDate: input.fromDate || null, toDate: input.toDate || null },\n updatedDateUtc: report.UpdatedDateUTC,\n headings,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21278
+ "scope": "read",
21279
+ "toolset": "reports"
21280
+ },
21281
+ {
21282
+ "name": "get_aged_payables_by_contact",
21283
+ "description": "Get the Aged Payables by Contact report for a supplier contact. Provide contactId from list_contacts.",
21284
+ "inputSchema": {
21285
+ "$schema": "http://json-schema.org/draft-07/schema#",
21286
+ "type": "object",
21287
+ "properties": {
21288
+ "tenantId": {
21289
+ "type": "string",
21290
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21291
+ },
21292
+ "contactId": {
21293
+ "type": "string",
21294
+ "description": "Xero contact ID."
21295
+ },
21296
+ "date": {
21297
+ "type": "string",
21298
+ "description": "Report date in YYYY-MM-DD format."
21299
+ }
21300
+ },
21301
+ "required": [
21302
+ "contactId"
21303
+ ],
21304
+ "additionalProperties": false
21305
+ },
21306
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.contactId) params.set('contactId', input.contactId)\n if (input.date) params.set('date', input.date)\n const res = await integration.get(`/api.xro/2.0/Reports/AgedPayablesByContact${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { date: input.date || null },\n updatedDateUtc: report.UpdatedDateUTC,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21307
+ "scope": "read",
21308
+ "toolset": "reports"
21309
+ },
21310
+ {
21311
+ "name": "get_aged_receivables_by_contact",
21312
+ "description": "Get the Aged Receivables by Contact report for a customer contact. Provide contactId from list_contacts.",
21313
+ "inputSchema": {
21314
+ "$schema": "http://json-schema.org/draft-07/schema#",
21315
+ "type": "object",
21316
+ "properties": {
21317
+ "tenantId": {
21318
+ "type": "string",
21319
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21320
+ },
21321
+ "contactId": {
21322
+ "type": "string",
21323
+ "description": "Xero contact ID."
21324
+ },
21325
+ "date": {
21326
+ "type": "string",
21327
+ "description": "Report date in YYYY-MM-DD format."
21328
+ }
21329
+ },
21330
+ "required": [
21331
+ "contactId"
21332
+ ],
21333
+ "additionalProperties": false
21334
+ },
21335
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.contactId) params.set('contactId', input.contactId)\n if (input.date) params.set('date', input.date)\n const res = await integration.get(`/api.xro/2.0/Reports/AgedReceivablesByContact${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { date: input.date || null },\n updatedDateUtc: report.UpdatedDateUTC,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21336
+ "scope": "read",
21337
+ "toolset": "reports"
21338
+ },
21339
+ {
21340
+ "name": "get_budget_summary",
21341
+ "description": "Get the Budget Summary report for a date range and period count.",
21342
+ "inputSchema": {
21343
+ "$schema": "http://json-schema.org/draft-07/schema#",
21344
+ "type": "object",
21345
+ "properties": {
21346
+ "tenantId": {
21347
+ "type": "string",
21348
+ "description": "Xero tenant ID from list_connections. Omit for Custom Connections."
21349
+ },
21350
+ "date": {
21351
+ "type": "string",
21352
+ "description": "Report date in YYYY-MM-DD format."
21353
+ },
21354
+ "periods": {
21355
+ "type": "integer",
21356
+ "minimum": 1,
21357
+ "description": "Number of periods."
21358
+ },
21359
+ "timeframe": {
21360
+ "type": "string",
21361
+ "enum": [
21362
+ "MONTH",
21363
+ "QUARTER",
21364
+ "YEAR"
21365
+ ],
21366
+ "description": "Period timeframe."
21367
+ }
21368
+ },
21369
+ "additionalProperties": false
21370
+ },
21371
+ "handlerCode": "async (input) => {\n const headers = input.tenantId ? { 'xero-tenant-id': input.tenantId } : {}\n const params = new URLSearchParams()\n if (input.date) params.set('date', input.date)\n if (input.periods) params.set('periods', String(input.periods))\n if (input.timeframe) params.set('timeframe', input.timeframe)\n const res = await integration.get(`/api.xro/2.0/Reports/BudgetSummary${params.toString() ? `?${params}` : ''}`, { headers })\n const data = await res.json()\n const report = Array.isArray(data?.Reports) ? data.Reports[0] : null\n const rows = Array.isArray(report?.Rows)\n ? report.Rows.map(row => ({\n rowType: row.RowType,\n title: row.Title,\n cells: Array.isArray(row.Cells) ? row.Cells.map(cell => cell.Value) : [],\n rows: Array.isArray(row.Rows)\n ? row.Rows.map(child => ({\n rowType: child.RowType,\n title: child.Title,\n cells: Array.isArray(child.Cells) ? child.Cells.map(cell => cell.Value) : [],\n }))\n : [],\n }))\n : []\n const headings = rows.find(row => row.rowType === 'Header')?.cells || []\n const summaryText = rows\n .filter(row => row.title || row.cells.length)\n .slice(0, 12)\n .map(row => [row.title || row.rowType, ...row.cells].filter(Boolean).join(' | '))\n .join('\\n')\n return {\n report: report\n ? {\n title: report.ReportTitles?.join(' - ') || report.ReportName,\n dateRange: { date: input.date || null },\n updatedDateUtc: report.UpdatedDateUTC,\n headings,\n rows,\n summaryText,\n }\n : null,\n query: Object.fromEntries(params.entries()),\n }\n}",
21372
+ "scope": "read",
21373
+ "toolset": "reports"
21374
+ }
21375
+ ],
21376
+ "variantOwnerType": null
21377
+ }
21378
+ };
21379
+
21380
+ function humanizeName(name) {
21381
+ return name.replace(/_/g, " ").split(/\s+/).filter(Boolean).map((w) => w.length ? `${w[0].toUpperCase()}${w.slice(1).toLowerCase()}` : w).join(" ");
21382
+ }
21383
+ function getIntegration(type) {
21384
+ var _a;
21385
+ return (_a = GENERATED_INTEGRATIONS[type]) != null ? _a : null;
21386
+ }
21387
+ function cloneManifest(manifest) {
21388
+ return {
21389
+ ...manifest,
21390
+ variantConfig: manifest.variantConfig ? JSON.parse(JSON.stringify(manifest.variantConfig)) : void 0,
21391
+ toolsets: manifest.toolsets ? { ...manifest.toolsets } : void 0,
21392
+ tools: manifest.tools.map((tool) => ({
21393
+ ...tool,
21394
+ injectFromConfig: tool.injectFromConfig ? { ...tool.injectFromConfig } : void 0
21395
+ }))
21396
+ };
21397
+ }
21398
+ function cloneCredentialVariant(variant) {
21399
+ var _a, _b;
21400
+ return {
21401
+ ...variant,
21402
+ injection: {
21403
+ headers: ((_a = variant.injection) == null ? void 0 : _a.headers) ? { ...variant.injection.headers } : void 0,
21404
+ query: ((_b = variant.injection) == null ? void 0 : _b.query) ? { ...variant.injection.query } : void 0
21405
+ },
21406
+ preprocess: typeof variant.preprocess === "object" && variant.preprocess !== null ? {
21407
+ ...variant.preprocess,
21408
+ allowedOrigins: Array.isArray(variant.preprocess.allowedOrigins) ? [...variant.preprocess.allowedOrigins] : void 0
21409
+ } : variant.preprocess,
21410
+ healthCheck: "path" in variant.healthCheck ? { ...variant.healthCheck } : { notViable: true }
21411
+ };
21412
+ }
21413
+ function validateCredentialVariant(type, variantKey, variant) {
21414
+ const healthCheck = variant.healthCheck;
21415
+ const hasHealthCheckPath = "path" in healthCheck && typeof healthCheck.path === "string" && healthCheck.path.trim().length > 0;
21416
+ const healthCheckNotViable = "notViable" in healthCheck && healthCheck.notViable === true;
21417
+ if (hasHealthCheckPath === healthCheckNotViable) {
21418
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': declare exactly one of 'healthCheck.path' or 'healthCheck.notViable: true'.`);
21419
+ }
21420
+ if (hasHealthCheckPath) {
21421
+ if ("method" in healthCheck && healthCheck.method !== void 0 && typeof healthCheck.method !== "string") {
21422
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.method must be a string when provided.`);
21423
+ }
21424
+ if ("headers" in healthCheck && healthCheck.headers !== void 0) {
21425
+ const headers = healthCheck.headers;
21426
+ if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
21427
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.headers must be an object when provided.`);
21428
+ }
21429
+ for (const [headerName, headerValue] of Object.entries(headers)) {
21430
+ if (!headerName.trim() || typeof headerValue !== "string") {
21431
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.headers must map non-empty names to string values.`);
21432
+ }
21433
+ }
21434
+ }
21435
+ if ("expectStatus" in healthCheck && healthCheck.expectStatus !== void 0) {
21436
+ const expectedStatuses = Array.isArray(healthCheck.expectStatus) ? healthCheck.expectStatus : [healthCheck.expectStatus];
21437
+ if (!expectedStatuses.length || expectedStatuses.some((status) => !Number.isInteger(status) || status < 100 || status > 599)) {
21438
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.expectStatus must be an HTTP status code or non-empty array of status codes.`);
21439
+ }
21440
+ }
21441
+ if ("description" in healthCheck && healthCheck.description !== void 0 && typeof healthCheck.description !== "string") {
21442
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.description must be a string when provided.`);
21443
+ }
21444
+ }
21445
+ if (healthCheckNotViable && "reason" in healthCheck && healthCheck.reason !== void 0 && typeof healthCheck.reason !== "string") {
21446
+ throw new Error(`Invalid credentials config for '${type}/${variantKey}': healthCheck.reason must be a string when provided.`);
21447
+ }
21448
+ }
21449
+ function validateCredentialVariantsFile(type, raw) {
21450
+ if (!(raw == null ? void 0 : raw.variants) || typeof raw.variants !== "object")
21451
+ throw new Error(`Invalid credentials config for '${type}': missing variants object.`);
21452
+ for (const [variantKey, variant] of Object.entries(raw.variants))
21453
+ validateCredentialVariant(type, variantKey, variant);
21454
+ return raw;
21455
+ }
21456
+ function cloneCredentialVariantsFile(type, variants) {
18696
21457
  const validated = validateCredentialVariantsFile(type, variants);
18697
21458
  return {
18698
21459
  default: validated.default,
@@ -18919,14 +21680,13 @@ function sanitizeJsonSchema(schema) {
18919
21680
  }
18920
21681
  return out;
18921
21682
  }
18922
- function makeIntegrationToolName(type, name, nodeId) {
18923
- const short = (nodeId || "").replace(/[^a-z0-9]/gi, "").slice(0, 8).toLowerCase();
18924
- let base = `${type}__${name}`.toLowerCase().replace(/[^a-z0-9_]/g, "_");
18925
- const suffix = `__n${short}`;
18926
- const maxBase = 64 - suffix.length;
18927
- if (base.length > maxBase)
18928
- base = base.slice(0, maxBase);
18929
- return `${base}${suffix}`;
21683
+ function sanitizeToolNamePart(value) {
21684
+ return (value || "").toLowerCase().replace(/[^a-z0-9_]/g, "_");
21685
+ }
21686
+ function makeIntegrationToolName(referenceId, name) {
21687
+ const integrationKey = sanitizeToolNamePart(referenceId);
21688
+ const toolKey = sanitizeToolNamePart(name);
21689
+ return `${integrationKey}__${toolKey}`;
18930
21690
  }
18931
21691
 
18932
21692
  const hoistedArtifactsByResult = /* @__PURE__ */ new WeakMap();
@@ -20315,6 +23075,7 @@ function ensureExtractorScript() {
20315
23075
  }
20316
23076
  const FILE_PROCESSING_DISABLED_TOOLS = {
20317
23077
  "google-workspace": ["read_file_content"],
23078
+ "google-gmail": ["read_attachment_content"],
20318
23079
  sharepoint: ["read_file_content"]
20319
23080
  };
20320
23081
  let capabilityPromise = null;
@@ -20417,6 +23178,9 @@ function isAbsoluteHttpUrl$1(value) {
20417
23178
  return false;
20418
23179
  }
20419
23180
  }
23181
+ function isDataUrl(value) {
23182
+ return String(value || "").trimStart().toLowerCase().startsWith("data:");
23183
+ }
20420
23184
  function parseContentDispositionFilename(value) {
20421
23185
  var _a;
20422
23186
  if (!value)
@@ -20485,6 +23249,35 @@ function inferFilename(response, source) {
20485
23249
  const ext = extensionFromContentType(response.headers.get("content-type"));
20486
23250
  return `downloaded-file${ext}`;
20487
23251
  }
23252
+ function parseDataUrl(source) {
23253
+ const match = String(source || "").match(/^data:([^,]*),(.*)$/s);
23254
+ if (!match)
23255
+ throw new HttpError(400, "Invalid data URL passed to extractFileContent.");
23256
+ const metadata = match[1] || "";
23257
+ const rawPayload = match[2] || "";
23258
+ const metadataParts = metadata.split(";").map((part) => part.trim()).filter(Boolean);
23259
+ const mimeType = metadataParts.find((part) => part.toLowerCase() !== "base64" && !part.includes("=")) || "application/octet-stream";
23260
+ const isBase64 = metadataParts.some((part) => part.toLowerCase() === "base64");
23261
+ if (!isBase64)
23262
+ throw new HttpError(400, "extractFileContent data URLs must use base64 encoding.");
23263
+ let payload = rawPayload.replace(/\s/g, "");
23264
+ try {
23265
+ payload = decodeURIComponent(payload);
23266
+ } catch {
23267
+ }
23268
+ if (!payload || payload.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(payload))
23269
+ throw new HttpError(400, "extractFileContent received an invalid base64 data URL payload.");
23270
+ return {
23271
+ bytes: Buffer.from(payload, "base64"),
23272
+ filename: `downloaded-file${extensionFromContentType(mimeType)}`
23273
+ };
23274
+ }
23275
+ async function readResponseFile(response, source) {
23276
+ return {
23277
+ bytes: Buffer.from(await response.arrayBuffer()),
23278
+ filename: inferFilename(response, source)
23279
+ };
23280
+ }
20488
23281
  async function downloadWithAuth(args, getIntegration) {
20489
23282
  if (!args.integration)
20490
23283
  throw new HttpError(400, "extractFileContent requires an exact integration id/reference when `auth` is true.");
@@ -20493,7 +23286,7 @@ async function downloadWithAuth(args, getIntegration) {
20493
23286
  }
20494
23287
  async function downloadWithoutAuth(args) {
20495
23288
  if (!isAbsoluteHttpUrl$1(args.source))
20496
- throw new HttpError(400, "extractFileContent requires an absolute http(s) URL when `auth` is false.");
23289
+ throw new HttpError(400, "extractFileContent requires an absolute http(s) URL or data URL when `auth` is false.");
20497
23290
  return fetch(args.source, { method: "GET" });
20498
23291
  }
20499
23292
  function createExtractFileContent(getIntegration, defaultIntegrationId) {
@@ -20508,18 +23301,22 @@ function createExtractFileContent(getIntegration, defaultIntegrationId) {
20508
23301
  const capability = await getFileProcessingCapability();
20509
23302
  if (!capability.enabled)
20510
23303
  throw new HttpError(501, formatFileProcessingUnavailableMessage(capability));
20511
- const response = resolvedArgs.auth ? await downloadWithAuth(resolvedArgs, getIntegration) : await downloadWithoutAuth(resolvedArgs);
20512
- if (!response.ok) {
20513
- const bodyText = await response.text().catch(() => "");
20514
- throw new HttpError(response.status, `Failed to download file (${response.status})${bodyText ? `: ${bodyText.slice(0, 500)}` : ""}.`);
20515
- }
23304
+ const downloaded = isDataUrl(resolvedArgs.source) ? !resolvedArgs.auth ? parseDataUrl(resolvedArgs.source) : (() => {
23305
+ throw new HttpError(400, "extractFileContent data URLs must use `auth: false`.");
23306
+ })() : await (async () => {
23307
+ const response = resolvedArgs.auth ? await downloadWithAuth(resolvedArgs, getIntegration) : await downloadWithoutAuth(resolvedArgs);
23308
+ if (!response.ok) {
23309
+ const bodyText = await response.text().catch(() => "");
23310
+ throw new HttpError(response.status, `Failed to download file (${response.status})${bodyText ? `: ${bodyText.slice(0, 500)}` : ""}.`);
23311
+ }
23312
+ return await readResponseFile(response, resolvedArgs.source);
23313
+ })();
20516
23314
  const tempDir = await mkdtemp(join(tmpdir(), "commandable-extract-"));
20517
23315
  try {
20518
- const filename = inferFilename(response, resolvedArgs.source);
23316
+ const filename = downloaded.filename;
20519
23317
  const filePath = join(tempDir, filename);
20520
23318
  const outputPath = join(tempDir, "result.json");
20521
- const bytes = Buffer.from(await response.arrayBuffer());
20522
- await writeFile$1(filePath, bytes);
23319
+ await writeFile$1(filePath, downloaded.bytes);
20523
23320
  const pythonArgs = [extractorScriptPath(), "--input", filePath, "--output", outputPath];
20524
23321
  const previewPages = typeof resolvedArgs.previewPages === "number" && resolvedArgs.previewPages > 0 ? Math.floor(resolvedArgs.previewPages) : 0;
20525
23322
  if (previewPages > 0)
@@ -20610,7 +23407,7 @@ function buildToolsByIntegration(spaceId, integrations, proxy, opts = {}) {
20610
23407
  const buildActions = (arr, scope) => arr.map((t) => {
20611
23408
  const rawSchema = typeof t.inputSchema === "string" ? JSON.parse(t.inputSchema) : t.inputSchema;
20612
23409
  const schemaObj = sanitizeJsonSchema(rawSchema);
20613
- const toolName = makeIntegrationToolName(integ.type, t.name, integ.id);
23410
+ const toolName = makeIntegrationToolName(integ.referenceId, t.name);
20614
23411
  const description = `[${integ.label} | ${integ.type}] ${t.description}`;
20615
23412
  const extractFileContent = createExtractFileContent(getIntegration, integ.id);
20616
23413
  const wrapper = buildHandlerWrapper(integ.id, t.handlerCode, integ.config, t.injectFromConfig);
@@ -20628,7 +23425,7 @@ function buildToolsByIntegration(spaceId, integrations, proxy, opts = {}) {
20628
23425
  });
20629
23426
  const buildActionsFromToolDefinitions = (defs, scope) => defs.map((t) => {
20630
23427
  const schemaObj = sanitizeJsonSchema(t.inputSchema);
20631
- const toolName = makeIntegrationToolName(integ.type, t.name, integ.id);
23428
+ const toolName = makeIntegrationToolName(integ.referenceId, t.name);
20632
23429
  const description = `[${integ.label} | ${integ.type}] ${t.description}`;
20633
23430
  const extractFileContent = createExtractFileContent(getIntegration, integ.id);
20634
23431
  const wrapper = `async (input) => {
@@ -21456,20 +24253,24 @@ async function checkIntegrationHealth(params) {
21456
24253
  const healthCheck = variant == null ? void 0 : variant.healthCheck;
21457
24254
  const path = healthCheck && "path" in healthCheck ? healthCheck.path : null;
21458
24255
  const method = healthCheck && "path" in healthCheck ? (_b = healthCheck.method) != null ? _b : "GET" : "GET";
24256
+ const headers = healthCheck && "path" in healthCheck ? healthCheck.headers : void 0;
24257
+ const expectedStatuses = healthCheck && "path" in healthCheck && healthCheck.expectStatus !== void 0 ? Array.isArray(healthCheck.expectStatus) ? healthCheck.expectStatus : [healthCheck.expectStatus] : [];
21459
24258
  if (!path) {
21460
24259
  return { status: "connected", skipped: true, checkedAt };
21461
24260
  }
21462
24261
  try {
21463
- await proxy.call(integration, path, { method });
24262
+ await proxy.call(integration, path, { method, headers });
21464
24263
  return { status: "connected", checkedAt };
21465
24264
  } catch (err) {
21466
- if ((err == null ? void 0 : err.statusCode) === 400 && ((_c = err == null ? void 0 : err.message) == null ? void 0 : _c.includes("No credentials"))) {
24265
+ const statusCode = (_c = err == null ? void 0 : err.statusCode) != null ? _c : null;
24266
+ if (typeof statusCode === "number" && expectedStatuses.includes(statusCode))
24267
+ return { status: "connected", checkedAt, message: err.message };
24268
+ if ((err == null ? void 0 : err.statusCode) === 400 && ((_d = err == null ? void 0 : err.message) == null ? void 0 : _d.includes("No credentials"))) {
21467
24269
  return { status: "disconnected", checkedAt, message: err.message };
21468
24270
  }
21469
- if ((err == null ? void 0 : err.statusCode) === 400 && ((_d = err == null ? void 0 : err.message) == null ? void 0 : _d.includes("credentialId"))) {
24271
+ if ((err == null ? void 0 : err.statusCode) === 400 && ((_e = err == null ? void 0 : err.message) == null ? void 0 : _e.includes("credentialId"))) {
21470
24272
  return { status: "disconnected", checkedAt, message: err.message };
21471
24273
  }
21472
- const statusCode = (_e = err == null ? void 0 : err.statusCode) != null ? _e : null;
21473
24274
  if (statusCode === 401) {
21474
24275
  return { status: "invalid_credentials", checkedAt, message: err.message };
21475
24276
  }
@@ -21875,9 +24676,6 @@ var __defProp$2 = Object.defineProperty;
21875
24676
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
21876
24677
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
21877
24678
  const SCOPE_RANK = { read: 0, write: 1, admin: 2 };
21878
- function shortNodeId(nodeId) {
21879
- return (nodeId || "").replace(/[^a-z0-9]/gi, "").slice(0, 8).toLowerCase();
21880
- }
21881
24679
  function humanize$1(s) {
21882
24680
  return (s || "").replace(/_/g, " ").split(/\s+/g).filter(Boolean).map((w) => w.length ? `${w[0].toUpperCase()}${w.slice(1).toLowerCase()}` : w).join(" ");
21883
24681
  }
@@ -21895,10 +24693,10 @@ const BUILDER_TOOL_NAMES = [
21895
24693
  "commandable_test_custom_tool"
21896
24694
  ];
21897
24695
  function makeAbilityId(integ, toolsetKey) {
21898
- const suffix = `__n${shortNodeId(integ.id)}`;
24696
+ const base = sanitizeAbilityKey(integ.referenceId);
21899
24697
  if (!toolsetKey)
21900
- return `${sanitizeAbilityKey(integ.type)}${suffix}`;
21901
- return `${sanitizeAbilityKey(integ.type)}__${sanitizeAbilityKey(toolsetKey)}${suffix}`;
24698
+ return base;
24699
+ return `${base}__${sanitizeAbilityKey(toolsetKey)}`;
21902
24700
  }
21903
24701
  function scoreQuery(query, haystack) {
21904
24702
  const q = (query || "").toLowerCase().trim();
@@ -22011,8 +24809,8 @@ class AbilityCatalog {
22011
24809
  return existing.toolNames.length !== before;
22012
24810
  }
22013
24811
  removeIntegrationAbilities(integration) {
22014
- const suffix = `__n${shortNodeId(integration.id)}`;
22015
- const removed = this.abilities.filter((a) => a.id.endsWith(suffix)).map((a) => a.id);
24812
+ const prefix = sanitizeAbilityKey(integration.referenceId);
24813
+ const removed = this.abilities.filter((a) => a.id === prefix || a.id.startsWith(`${prefix}__`)).map((a) => a.id);
22016
24814
  if (!removed.length)
22017
24815
  return 0;
22018
24816
  for (const id of removed)
@@ -22086,7 +24884,7 @@ class AbilityCatalog {
22086
24884
  continue;
22087
24885
  if (blocked == null ? void 0 : blocked.has(ref.name))
22088
24886
  continue;
22089
- const toolName = makeIntegrationToolName(integ.type, ref.name, integ.id);
24887
+ const toolName = makeIntegrationToolName(integ.referenceId, ref.name);
22090
24888
  if (!this.toolIndex.has(toolName))
22091
24889
  continue;
22092
24890
  allTools.push(toolName);
@@ -22096,12 +24894,9 @@ class AbilityCatalog {
22096
24894
  toolsByToolset.set(bucketKey, arr);
22097
24895
  }
22098
24896
  }
22099
- const suffix = `__n${shortNodeId(integ.id)}`;
22100
- const typePrefix = `${sanitizeAbilityKey(integ.type)}__`;
24897
+ const referencePrefix = `${sanitizeAbilityKey(integ.referenceId)}__`;
22101
24898
  const allForIntegration = [...this.toolIndex.keys()].filter((n) => {
22102
- if (!n.endsWith(suffix))
22103
- return false;
22104
- if (!n.startsWith(typePrefix))
24899
+ if (!n.startsWith(referencePrefix))
22105
24900
  return false;
22106
24901
  return true;
22107
24902
  });
@@ -22181,7 +24976,7 @@ function buildExecutableToolFromDefinition(params) {
22181
24976
  extractFileContent: createExtractFileContent(getIntegration, integration.id)
22182
24977
  });
22183
24978
  const scope = tool.scope || "write";
22184
- const toolName = makeIntegrationToolName(integration.type, tool.name, integration.id);
24979
+ const toolName = makeIntegrationToolName(integration.referenceId, tool.name);
22185
24980
  const description = `[${integration.label} | ${integration.type}] ${tool.description || tool.displayName || tool.name}`;
22186
24981
  const inputSchema = sanitizeJsonSchema(tool.inputSchema || { type: "object", additionalProperties: true });
22187
24982
  const wrapper = `async (input) => {
@@ -23047,7 +25842,7 @@ ${lines ? `${lines}
23047
25842
  const integration = ctx.integrationsRef.current.find((i) => i.id === integrationId || i.referenceId === integrationId);
23048
25843
  if (!integration)
23049
25844
  throw new Error(`Unknown integration_id: ${integrationId}`);
23050
- const materializedToolNames = [...((_h = ctx.toolIndexRef) == null ? void 0 : _h.byName.keys()) || []].filter((toolName) => toolName.endsWith(`__n${integration.id.replace(/[^a-z0-9]/gi, "").slice(0, 8).toLowerCase()}`));
25845
+ const materializedToolNames = [...((_h = ctx.toolIndexRef) == null ? void 0 : _h.byName.keys()) || []].filter((toolName) => toolName.startsWith(`${integration.referenceId.replace(/[^a-z0-9_]/gi, "_").toLowerCase()}__`));
23051
25846
  for (const toolName of materializedToolNames) {
23052
25847
  (_i = ctx.toolIndexRef) == null ? void 0 : _i.byName.delete(toolName);
23053
25848
  sessionState.removeToolFromAllSessions(toolName);
@@ -23569,7 +26364,7 @@ function hasAutoConfigFile() {
23569
26364
  ].map((f) => resolve$1(base, f));
23570
26365
  return candidates.some((p) => existsSync(p) && statSync(p).isFile());
23571
26366
  }
23572
- const _M_TVKJFSaqgo9E7nGBulJC6Bj53qbW6Kmjg2BjXUCEw = defineNitroPlugin(async () => {
26367
+ const _AquVwiIVM7a1K2p3HKZndtheEQgWg5MseXxRS5CsE6E = defineNitroPlugin(async () => {
23573
26368
  const explicit = process.env.COMMANDABLE_CONFIG_FILE;
23574
26369
  if (!explicit && !hasAutoConfigFile())
23575
26370
  return;
@@ -23588,7 +26383,7 @@ const _M_TVKJFSaqgo9E7nGBulJC6Bj53qbW6Kmjg2BjXUCEw = defineNitroPlugin(async ()
23588
26383
  }
23589
26384
  });
23590
26385
 
23591
- const _ZP9JqzcUWiqZ00FMxCGYad0cri1qDPlZLfZD4ElgrrI = defineNitroPlugin(async () => {
26386
+ const _lVWhYLiBO2LPJ2uGgGmGMgzdCSfVXfio7QJNE3KXLWg = defineNitroPlugin(async () => {
23592
26387
  try {
23593
26388
  const capability = await warmFileProcessingCapability();
23594
26389
  if (!capability.enabled)
@@ -23600,157 +26395,157 @@ const _ZP9JqzcUWiqZ00FMxCGYad0cri1qDPlZLfZD4ElgrrI = defineNitroPlugin(async ()
23600
26395
  }
23601
26396
  });
23602
26397
 
23603
- const _yMUFGVAX_r3fg7CMgx97QTG6uaKiBwq9aOXHWvCclw = defineNitroPlugin(() => {
26398
+ const _1dydEfKs62cALEMU8o0N971FGk1682y6tf9StPR8As = defineNitroPlugin(() => {
23604
26399
  getOrCreateEncryptionSecret();
23605
26400
  });
23606
26401
 
23607
26402
  const plugins = [
23608
- _z25E5bI6lxolM40nRjOSnV7rTGIEhhG_oLaxlR5RK0I,
23609
- _M_TVKJFSaqgo9E7nGBulJC6Bj53qbW6Kmjg2BjXUCEw,
23610
- _ZP9JqzcUWiqZ00FMxCGYad0cri1qDPlZLfZD4ElgrrI,
23611
- _yMUFGVAX_r3fg7CMgx97QTG6uaKiBwq9aOXHWvCclw
26403
+ _zUuxVGgAOQhH7E5atfn9Ptf95wF4esqexnSnzyTPmK8,
26404
+ _AquVwiIVM7a1K2p3HKZndtheEQgWg5MseXxRS5CsE6E,
26405
+ _lVWhYLiBO2LPJ2uGgGmGMgzdCSfVXfio7QJNE3KXLWg,
26406
+ _1dydEfKs62cALEMU8o0N971FGk1682y6tf9StPR8As
23612
26407
  ];
23613
26408
 
23614
26409
  const assets = {
23615
26410
  "/favicon.ico": {
23616
26411
  "type": "image/vnd.microsoft.icon",
23617
26412
  "etag": "\"10be-n8egyE9tcb7sKGr/pYCaQ4uWqxI\"",
23618
- "mtime": "2026-04-12T18:58:38.513Z",
26413
+ "mtime": "2026-04-28T17:57:48.323Z",
23619
26414
  "size": 4286,
23620
26415
  "path": "../public/favicon.ico"
23621
26416
  },
23622
26417
  "/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2": {
23623
26418
  "type": "font/woff2",
23624
26419
  "etag": "\"4b5c-TAo9mx7r3xQs52+HbHcHJ52z8Qo\"",
23625
- "mtime": "2026-04-12T18:58:38.507Z",
26420
+ "mtime": "2026-04-28T17:57:48.317Z",
23626
26421
  "size": 19292,
23627
26422
  "path": "../public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2"
23628
26423
  },
23629
26424
  "/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2": {
23630
26425
  "type": "font/woff2",
23631
26426
  "etag": "\"212c-FshXJibFzNhd2HEIMP8C3JR5PYg\"",
23632
- "mtime": "2026-04-12T18:58:38.507Z",
26427
+ "mtime": "2026-04-28T17:57:48.318Z",
23633
26428
  "size": 8492,
23634
26429
  "path": "../public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2"
23635
26430
  },
23636
26431
  "/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2": {
23637
26432
  "type": "font/woff2",
23638
26433
  "etag": "\"680c-mJtsV33lkTAKSmfq5k3lKHSllcU\"",
23639
- "mtime": "2026-04-12T18:58:38.507Z",
26434
+ "mtime": "2026-04-28T17:57:48.318Z",
23640
26435
  "size": 26636,
23641
26436
  "path": "../public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2"
23642
26437
  },
23643
26438
  "/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2": {
23644
26439
  "type": "font/woff2",
23645
26440
  "etag": "\"6ec4-8OoFFPZKF1grqmfGVjh5JDE6DOU\"",
23646
- "mtime": "2026-04-12T18:58:38.507Z",
26441
+ "mtime": "2026-04-28T17:57:48.318Z",
23647
26442
  "size": 28356,
23648
26443
  "path": "../public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2"
23649
26444
  },
23650
26445
  "/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2": {
23651
26446
  "type": "font/woff2",
23652
26447
  "etag": "\"1d98-cDZfMibtk4T04FTTAmlfhWDpkN0\"",
23653
- "mtime": "2026-04-12T18:58:38.507Z",
26448
+ "mtime": "2026-04-28T17:57:48.318Z",
23654
26449
  "size": 7576,
23655
26450
  "path": "../public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2"
23656
26451
  },
23657
26452
  "/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2": {
23658
26453
  "type": "font/woff2",
23659
26454
  "etag": "\"47c4-5xyngHnzzhetUee74tMx9OTgqNQ\"",
23660
- "mtime": "2026-04-12T18:58:38.507Z",
26455
+ "mtime": "2026-04-28T17:57:48.318Z",
23661
26456
  "size": 18372,
23662
26457
  "path": "../public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2"
23663
26458
  },
23664
- "/_nuxt/BD6mASiY.js": {
26459
+ "/_nuxt/BZ8athzM.js": {
23665
26460
  "type": "text/javascript; charset=utf-8",
23666
- "etag": "\"ab-ScyLcA/4r5aOxEv1YY+kqXazCHI\"",
23667
- "mtime": "2026-04-12T18:58:38.509Z",
23668
- "size": 171,
23669
- "path": "../public/_nuxt/BD6mASiY.js"
26461
+ "etag": "\"d7b-bo5LDZYg8h0KJrRJjoz1tlPmdRo\"",
26462
+ "mtime": "2026-04-28T17:57:48.321Z",
26463
+ "size": 3451,
26464
+ "path": "../public/_nuxt/BZ8athzM.js"
23670
26465
  },
23671
- "/_nuxt/D9wFDhac.js": {
26466
+ "/_nuxt/CiSdBjBt.js": {
23672
26467
  "type": "text/javascript; charset=utf-8",
23673
- "etag": "\"e99-sUFV1wmMOK2XGfzDXJyP2NA8TG4\"",
23674
- "mtime": "2026-04-12T18:58:38.510Z",
26468
+ "etag": "\"e99-9LMfGEEWjCGZ3Oa0GdbKNntD/tc\"",
26469
+ "mtime": "2026-04-28T17:57:48.321Z",
23675
26470
  "size": 3737,
23676
- "path": "../public/_nuxt/D9wFDhac.js"
26471
+ "path": "../public/_nuxt/CiSdBjBt.js"
23677
26472
  },
23678
- "/_nuxt/CjAs3eBq.js": {
26473
+ "/_nuxt/Vt66r6sN.js": {
23679
26474
  "type": "text/javascript; charset=utf-8",
23680
- "etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
23681
- "mtime": "2026-04-12T18:58:38.510Z",
26475
+ "etag": "\"1df7-3bELG1uFN8suLzh1cLFlZbYl6zg\"",
26476
+ "mtime": "2026-04-28T17:57:48.321Z",
23682
26477
  "size": 7671,
23683
- "path": "../public/_nuxt/CjAs3eBq.js"
23684
- },
23685
- "/_nuxt/DSWYWRXT.js": {
23686
- "type": "text/javascript; charset=utf-8",
23687
- "etag": "\"10875-8b+YwIvP6QkcBFnHXqxd+WeZ05o\"",
23688
- "mtime": "2026-04-12T18:58:38.510Z",
23689
- "size": 67701,
23690
- "path": "../public/_nuxt/DSWYWRXT.js"
26478
+ "path": "../public/_nuxt/Vt66r6sN.js"
23691
26479
  },
23692
- "/_nuxt/DRfk9W3W.js": {
26480
+ "/_nuxt/B6ti3873.js": {
23693
26481
  "type": "text/javascript; charset=utf-8",
23694
- "etag": "\"194dc-Oj5Ixz12+pq4yqDtF/N+YAPzoWw\"",
23695
- "mtime": "2026-04-12T18:58:38.510Z",
26482
+ "etag": "\"194dc-0jLXXucZxinj1WIeQALJhFSiqBM\"",
26483
+ "mtime": "2026-04-28T17:57:48.321Z",
23696
26484
  "size": 103644,
23697
- "path": "../public/_nuxt/DRfk9W3W.js"
26485
+ "path": "../public/_nuxt/B6ti3873.js"
23698
26486
  },
23699
- "/_nuxt/VvnbcAzZ.js": {
26487
+ "/_nuxt/BuJvZwTh.js": {
23700
26488
  "type": "text/javascript; charset=utf-8",
23701
- "etag": "\"d7b-hU4O5jppM7Ou3kZAYy3iYXlgoa8\"",
23702
- "mtime": "2026-04-12T18:58:38.510Z",
23703
- "size": 3451,
23704
- "path": "../public/_nuxt/VvnbcAzZ.js"
26489
+ "etag": "\"10875-jI7p7PC8XYTrsiB6P1KTm9OW02I\"",
26490
+ "mtime": "2026-04-28T17:57:48.321Z",
26491
+ "size": 67701,
26492
+ "path": "../public/_nuxt/BuJvZwTh.js"
23705
26493
  },
23706
26494
  "/_nuxt/_id_.DhlLK-mY.css": {
23707
26495
  "type": "text/css; charset=utf-8",
23708
26496
  "etag": "\"2f4-xtV37kE566jU74wpZnFHA29RoAY\"",
23709
- "mtime": "2026-04-12T18:58:38.510Z",
26497
+ "mtime": "2026-04-28T17:57:48.321Z",
23710
26498
  "size": 756,
23711
26499
  "path": "../public/_nuxt/_id_.DhlLK-mY.css"
23712
26500
  },
26501
+ "/_nuxt/D_Ecm3JY.js": {
26502
+ "type": "text/javascript; charset=utf-8",
26503
+ "etag": "\"ab-OTrt3/Pw7AlxGhPlIrQjGFjRSR4\"",
26504
+ "mtime": "2026-04-28T17:57:48.321Z",
26505
+ "size": 171,
26506
+ "path": "../public/_nuxt/D_Ecm3JY.js"
26507
+ },
23713
26508
  "/_nuxt/error-404.C7fg894-.css": {
23714
26509
  "type": "text/css; charset=utf-8",
23715
26510
  "etag": "\"97e-fiQ3o7A11L9BuXRBr0GJldkx0AU\"",
23716
- "mtime": "2026-04-12T18:58:38.510Z",
26511
+ "mtime": "2026-04-28T17:57:48.321Z",
23717
26512
  "size": 2430,
23718
26513
  "path": "../public/_nuxt/error-404.C7fg894-.css"
23719
26514
  },
23720
26515
  "/_nuxt/error-500.DjUK_N2Y.css": {
23721
26516
  "type": "text/css; charset=utf-8",
23722
26517
  "etag": "\"773-Qf61bSDos4KtmZDaA06FmZyUYNo\"",
23723
- "mtime": "2026-04-12T18:58:38.510Z",
26518
+ "mtime": "2026-04-28T17:57:48.321Z",
23724
26519
  "size": 1907,
23725
26520
  "path": "../public/_nuxt/error-500.DjUK_N2Y.css"
23726
26521
  },
23727
26522
  "/_nuxt/builds/latest.json": {
23728
26523
  "type": "application/json",
23729
- "etag": "\"47-e/86GxbuxL65vE0JCcC0T3jGAp4\"",
23730
- "mtime": "2026-04-12T18:58:38.505Z",
26524
+ "etag": "\"47-TQX72Swoa7t4jw5HcBGsQR8FzY0\"",
26525
+ "mtime": "2026-04-28T17:57:48.316Z",
23731
26526
  "size": 71,
23732
26527
  "path": "../public/_nuxt/builds/latest.json"
23733
26528
  },
23734
- "/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json": {
26529
+ "/_nuxt/builds/meta/6152dcdc-6d3b-4e0a-8c56-4584c72c8765.json": {
23735
26530
  "type": "application/json",
23736
- "etag": "\"58-BEsLMe80cnxppLc/ZshfoAR3oqs\"",
23737
- "mtime": "2026-04-12T18:58:38.503Z",
26531
+ "etag": "\"58-d/z3J0a3FmSy2TB7Una/XGJcn94\"",
26532
+ "mtime": "2026-04-28T17:57:48.314Z",
23738
26533
  "size": 88,
23739
- "path": "../public/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json"
23740
- },
23741
- "/_nuxt/BUmYUDQu.js": {
23742
- "type": "text/javascript; charset=utf-8",
23743
- "etag": "\"66cba-d/pdEXVc78H3VlgFN3kVzKpvD1Q\"",
23744
- "mtime": "2026-04-12T18:58:38.510Z",
23745
- "size": 421050,
23746
- "path": "../public/_nuxt/BUmYUDQu.js"
26534
+ "path": "../public/_nuxt/builds/meta/6152dcdc-6d3b-4e0a-8c56-4584c72c8765.json"
23747
26535
  },
23748
26536
  "/_nuxt/entry.Y3mA4bzA.css": {
23749
26537
  "type": "text/css; charset=utf-8",
23750
26538
  "etag": "\"2d46b-zfrD3Ny9WW6qm4fCXAfX5eIAxPA\"",
23751
- "mtime": "2026-04-12T18:58:38.510Z",
26539
+ "mtime": "2026-04-28T17:57:48.321Z",
23752
26540
  "size": 185451,
23753
26541
  "path": "../public/_nuxt/entry.Y3mA4bzA.css"
26542
+ },
26543
+ "/_nuxt/bOjQRRUc.js": {
26544
+ "type": "text/javascript; charset=utf-8",
26545
+ "etag": "\"66cba-ROXbp6pQG1qRn50SMispE0HMqw0\"",
26546
+ "mtime": "2026-04-28T17:57:48.322Z",
26547
+ "size": 421050,
26548
+ "path": "../public/_nuxt/bOjQRRUc.js"
23754
26549
  }
23755
26550
  };
23756
26551
 
@@ -23892,7 +26687,7 @@ function getAsset (id) {
23892
26687
 
23893
26688
  const METHODS = /* @__PURE__ */ new Set(["HEAD", "GET"]);
23894
26689
  const EncodingMap = { gzip: ".gz", br: ".br" };
23895
- const _uVCD2E = eventHandler((event) => {
26690
+ const _nP7_Js = eventHandler((event) => {
23896
26691
  if (event.method && !METHODS.has(event.method)) {
23897
26692
  return;
23898
26693
  }
@@ -23960,7 +26755,7 @@ const BEARER_PREFIX_RE = /^Bearer[ \t]+/i;
23960
26755
  function isMcpPath(pathname) {
23961
26756
  return pathname === "/mcp" || pathname.startsWith("/mcp/");
23962
26757
  }
23963
- const _8MEaQo = defineEventHandler(async (event) => {
26758
+ const _VsXl2a = defineEventHandler(async (event) => {
23964
26759
  var _a, _b;
23965
26760
  if (!isMcpPath(event.path))
23966
26761
  return;
@@ -24297,7 +27092,7 @@ const collections = {
24297
27092
  };
24298
27093
 
24299
27094
  const DEFAULT_ENDPOINT = "https://api.iconify.design";
24300
- const _qZilHW = defineCachedEventHandler(async (event) => {
27095
+ const _07evdj = defineCachedEventHandler(async (event) => {
24301
27096
  const url = getRequestURL(event);
24302
27097
  if (!url)
24303
27098
  return createError$1({ status: 400, message: "Invalid icon request" });
@@ -24347,57 +27142,57 @@ const _qZilHW = defineCachedEventHandler(async (event) => {
24347
27142
  // 1 week
24348
27143
  });
24349
27144
 
24350
- const _lazy_9UBi6Z = () => import('../routes/api/_commandable/status.get.mjs');
24351
- const _lazy_L27xgp = () => import('../routes/api/catalog.get.mjs');
24352
- const _lazy_Qb7c8O = () => import('../routes/api/catalog/_type/tools.get.mjs');
24353
- const _lazy_6zs_jK = () => import('../routes/api/catalog/_type/toolsets.get.mjs');
24354
- const _lazy_fHARHm = () => import('../routes/api/integrations/_id_.delete.mjs');
24355
- const _lazy_9jDGT8 = () => import('../routes/api/integrations/_id/credentials-config.get.mjs');
24356
- const _lazy_L7hEAa = () => import('../routes/api/integrations/_id/credentials-status.get.mjs');
24357
- const _lazy_YnK3y3 = () => import('../routes/api/integrations/_id/credentials.delete.mjs');
24358
- const _lazy_Pjbjhf = () => import('../routes/api/integrations/_id/credentials.post.mjs');
24359
- const _lazy_NxrILm = () => import('../routes/api/integrations/_id/permissions.post.mjs');
24360
- const _lazy_tZWrQN = () => import('../routes/api/integrations/_id/tools.delete.mjs');
24361
- const _lazy_zaazLC = () => import('../routes/api/integrations/_id/tools.get.mjs');
24362
- const _lazy_9iz_Is = () => import('../routes/api/integrations/_id/toolsets.get.mjs');
24363
- const _lazy_Klwsdp = () => import('../routes/api/integrations/_id/toolsets.post.mjs');
24364
- const _lazy_Wj3Kn6 = () => import('../routes/api/integrations/_id/variant-options.post.mjs');
24365
- const _lazy_FcbHxS = () => import('../routes/api/index.get.mjs');
24366
- const _lazy_0b9jj9 = () => import('../routes/api/index.post.mjs');
24367
- const _lazy_g_ZIqQ = () => import('../routes/health.get.mjs');
24368
- const _lazy_t96RTY = () => import('../routes/mcp.mjs');
24369
- const _lazy_j73HEu = () => import('../routes/mcp/create.mjs');
24370
- const _lazy_rOVVft = () => import('../routes/mcp/static.mjs');
24371
- const _lazy_8kkCeS = () => import('../routes/renderer.mjs').then(function (n) { return n.r; });
27145
+ const _lazy_rQMQx5 = () => import('../routes/api/_commandable/status.get.mjs');
27146
+ const _lazy__doBUX = () => import('../routes/api/catalog.get.mjs');
27147
+ const _lazy_u4B20q = () => import('../routes/api/catalog/_type/tools.get.mjs');
27148
+ const _lazy_029V4o = () => import('../routes/api/catalog/_type/toolsets.get.mjs');
27149
+ const _lazy_azzc35 = () => import('../routes/api/integrations/_id_.delete.mjs');
27150
+ const _lazy_gWcuQ4 = () => import('../routes/api/integrations/_id/credentials-config.get.mjs');
27151
+ const _lazy_n3SXvL = () => import('../routes/api/integrations/_id/credentials-status.get.mjs');
27152
+ const _lazy_1ybFiB = () => import('../routes/api/integrations/_id/credentials.delete.mjs');
27153
+ const _lazy_Aos7tC = () => import('../routes/api/integrations/_id/credentials.post.mjs');
27154
+ const _lazy_Gup8dS = () => import('../routes/api/integrations/_id/permissions.post.mjs');
27155
+ const _lazy_4NNYku = () => import('../routes/api/integrations/_id/tools.delete.mjs');
27156
+ const _lazy_26WqJh = () => import('../routes/api/integrations/_id/tools.get.mjs');
27157
+ const _lazy_vPm_0i = () => import('../routes/api/integrations/_id/toolsets.get.mjs');
27158
+ const _lazy_5oSjkc = () => import('../routes/api/integrations/_id/toolsets.post.mjs');
27159
+ const _lazy_HaRaH2 = () => import('../routes/api/integrations/_id/variant-options.post.mjs');
27160
+ const _lazy_xaif49 = () => import('../routes/api/index.get.mjs');
27161
+ const _lazy_JZIC7v = () => import('../routes/api/index.post.mjs');
27162
+ const _lazy_jGrRMD = () => import('../routes/health.get.mjs');
27163
+ const _lazy_o0bRnv = () => import('../routes/mcp.mjs');
27164
+ const _lazy_nXZgsW = () => import('../routes/mcp/create.mjs');
27165
+ const _lazy_ArDuzZ = () => import('../routes/mcp/static.mjs');
27166
+ const _lazy_FEvF4o = () => import('../routes/renderer.mjs').then(function (n) { return n.r; });
24372
27167
 
24373
27168
  const handlers = [
24374
- { route: '', handler: _uVCD2E, lazy: false, middleware: true, method: undefined },
24375
- { route: '', handler: _8MEaQo, lazy: false, middleware: true, method: undefined },
24376
- { route: '/api/_commandable/status', handler: _lazy_9UBi6Z, lazy: true, middleware: false, method: "get" },
24377
- { route: '/api/catalog', handler: _lazy_L27xgp, lazy: true, middleware: false, method: "get" },
24378
- { route: '/api/catalog/:type/tools', handler: _lazy_Qb7c8O, lazy: true, middleware: false, method: "get" },
24379
- { route: '/api/catalog/:type/toolsets', handler: _lazy_6zs_jK, lazy: true, middleware: false, method: "get" },
24380
- { route: '/api/integrations/:id', handler: _lazy_fHARHm, lazy: true, middleware: false, method: "delete" },
24381
- { route: '/api/integrations/:id/credentials-config', handler: _lazy_9jDGT8, lazy: true, middleware: false, method: "get" },
24382
- { route: '/api/integrations/:id/credentials-status', handler: _lazy_L7hEAa, lazy: true, middleware: false, method: "get" },
24383
- { route: '/api/integrations/:id/credentials', handler: _lazy_YnK3y3, lazy: true, middleware: false, method: "delete" },
24384
- { route: '/api/integrations/:id/credentials', handler: _lazy_Pjbjhf, lazy: true, middleware: false, method: "post" },
24385
- { route: '/api/integrations/:id/permissions', handler: _lazy_NxrILm, lazy: true, middleware: false, method: "post" },
24386
- { route: '/api/integrations/:id/tools', handler: _lazy_tZWrQN, lazy: true, middleware: false, method: "delete" },
24387
- { route: '/api/integrations/:id/tools', handler: _lazy_zaazLC, lazy: true, middleware: false, method: "get" },
24388
- { route: '/api/integrations/:id/toolsets', handler: _lazy_9iz_Is, lazy: true, middleware: false, method: "get" },
24389
- { route: '/api/integrations/:id/toolsets', handler: _lazy_Klwsdp, lazy: true, middleware: false, method: "post" },
24390
- { route: '/api/integrations/:id/variant-options', handler: _lazy_Wj3Kn6, lazy: true, middleware: false, method: "post" },
24391
- { route: '/api/integrations', handler: _lazy_FcbHxS, lazy: true, middleware: false, method: "get" },
24392
- { route: '/api/integrations', handler: _lazy_0b9jj9, lazy: true, middleware: false, method: "post" },
24393
- { route: '/health', handler: _lazy_g_ZIqQ, lazy: true, middleware: false, method: "get" },
24394
- { route: '/mcp', handler: _lazy_t96RTY, lazy: true, middleware: false, method: undefined },
24395
- { route: '/mcp/create', handler: _lazy_j73HEu, lazy: true, middleware: false, method: undefined },
24396
- { route: '/mcp/static', handler: _lazy_rOVVft, lazy: true, middleware: false, method: undefined },
24397
- { route: '/__nuxt_error', handler: _lazy_8kkCeS, lazy: true, middleware: false, method: undefined },
27169
+ { route: '', handler: _nP7_Js, lazy: false, middleware: true, method: undefined },
27170
+ { route: '', handler: _VsXl2a, lazy: false, middleware: true, method: undefined },
27171
+ { route: '/api/_commandable/status', handler: _lazy_rQMQx5, lazy: true, middleware: false, method: "get" },
27172
+ { route: '/api/catalog', handler: _lazy__doBUX, lazy: true, middleware: false, method: "get" },
27173
+ { route: '/api/catalog/:type/tools', handler: _lazy_u4B20q, lazy: true, middleware: false, method: "get" },
27174
+ { route: '/api/catalog/:type/toolsets', handler: _lazy_029V4o, lazy: true, middleware: false, method: "get" },
27175
+ { route: '/api/integrations/:id', handler: _lazy_azzc35, lazy: true, middleware: false, method: "delete" },
27176
+ { route: '/api/integrations/:id/credentials-config', handler: _lazy_gWcuQ4, lazy: true, middleware: false, method: "get" },
27177
+ { route: '/api/integrations/:id/credentials-status', handler: _lazy_n3SXvL, lazy: true, middleware: false, method: "get" },
27178
+ { route: '/api/integrations/:id/credentials', handler: _lazy_1ybFiB, lazy: true, middleware: false, method: "delete" },
27179
+ { route: '/api/integrations/:id/credentials', handler: _lazy_Aos7tC, lazy: true, middleware: false, method: "post" },
27180
+ { route: '/api/integrations/:id/permissions', handler: _lazy_Gup8dS, lazy: true, middleware: false, method: "post" },
27181
+ { route: '/api/integrations/:id/tools', handler: _lazy_4NNYku, lazy: true, middleware: false, method: "delete" },
27182
+ { route: '/api/integrations/:id/tools', handler: _lazy_26WqJh, lazy: true, middleware: false, method: "get" },
27183
+ { route: '/api/integrations/:id/toolsets', handler: _lazy_vPm_0i, lazy: true, middleware: false, method: "get" },
27184
+ { route: '/api/integrations/:id/toolsets', handler: _lazy_5oSjkc, lazy: true, middleware: false, method: "post" },
27185
+ { route: '/api/integrations/:id/variant-options', handler: _lazy_HaRaH2, lazy: true, middleware: false, method: "post" },
27186
+ { route: '/api/integrations', handler: _lazy_xaif49, lazy: true, middleware: false, method: "get" },
27187
+ { route: '/api/integrations', handler: _lazy_JZIC7v, lazy: true, middleware: false, method: "post" },
27188
+ { route: '/health', handler: _lazy_jGrRMD, lazy: true, middleware: false, method: "get" },
27189
+ { route: '/mcp', handler: _lazy_o0bRnv, lazy: true, middleware: false, method: undefined },
27190
+ { route: '/mcp/create', handler: _lazy_nXZgsW, lazy: true, middleware: false, method: undefined },
27191
+ { route: '/mcp/static', handler: _lazy_ArDuzZ, lazy: true, middleware: false, method: undefined },
27192
+ { route: '/__nuxt_error', handler: _lazy_FEvF4o, lazy: true, middleware: false, method: undefined },
24398
27193
  { route: '/__nuxt_island/**', handler: _SxA8c9, lazy: false, middleware: false, method: undefined },
24399
- { route: '/api/_nuxt_icon/:collection', handler: _qZilHW, lazy: false, middleware: false, method: undefined },
24400
- { route: '/**', handler: _lazy_8kkCeS, lazy: true, middleware: false, method: undefined }
27194
+ { route: '/api/_nuxt_icon/:collection', handler: _07evdj, lazy: false, middleware: false, method: undefined },
27195
+ { route: '/**', handler: _lazy_FEvF4o, lazy: true, middleware: false, method: undefined }
24401
27196
  ];
24402
27197
 
24403
27198
  function createNitroApp() {