beeswax-mcp 1.6.0 → 2.0.0

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.
package/README.md CHANGED
@@ -150,10 +150,16 @@ reports the same on demand.
150
150
  | `list_themes`, `get_theme_spec`, `get_theme`, `upsert_theme` (write), `validate_theme` (write), `preview_theme`, `activate_theme` (write), `delete_theme` (write) | `/themes` (custom document themes, THEME_SPEC v1) |
151
151
  | `list_products_services`, `get_product_service`, `create_product_service` (write), `update_product_service` (write), `delete_product_service` (write) | `/transaction_templates` |
152
152
  | `list_manual_journals`, `get_manual_journal`, `create_manual_journal` (write), `finalise_manual_journal` (write) | `/raw_journal_entries` |
153
- | `list_tax_returns`, `get_tax_return`, `get_tax_return_field_transactions`, `list_tax_return_findings`, `update_tax_return_field` (write), `confirm_tax_return_section` (write) | `/tax_returns` |
153
+ | `list_tax_returns`, `get_tax_return`, `get_tax_return_field_transactions`, `compare_tax_returns`, `tax_return_preflight_check`, `list_tax_return_field_notes`, `get_tax_return_conversation`, `create_tax_return` (write), `repopulate_tax_return` (write), `update_tax_return_field` (write), `add_tax_return_field_note` (write), `edit_tax_return_field_note` (write), `post_tax_return_message` (write), `confirm_tax_return_section` / `unconfirm_tax_return_section` (write), `finalise_tax_return` / `unfinalise_tax_return` (write) | `/tax_returns` — the assistant is the reviewer: it reads the figures and the transactions behind them and records its reasoning as field notes and discussion messages |
154
154
 
155
155
  ### Notes
156
156
 
157
+ - The create tools and `apply_payment` accept `idempotency_key`. Reuse it on a
158
+ retry and the API replays the original result instead of posting twice.
159
+ - Every `list_*` tool accepts `updated_since` (ISO 8601) so an agent can ask
160
+ for "what changed since yesterday" instead of re-reading everything; rows
161
+ carry `updated_at`.
162
+
157
163
  - **Scopes.** `query_journal_entries` / `get_journal_entry` need an `all`-scoped
158
164
  token for a bare listing, a show, or a long-tail type (credits, bank_transfers,
159
165
  payrolls); a typed listing (`type: "invoices"`) accepts that type's read scope.
package/dist/client.js CHANGED
@@ -124,18 +124,23 @@ export class BeeswaxClient {
124
124
  }
125
125
  // Write request (POST / PATCH / DELETE) with a JSON body. Returns the parsed
126
126
  // response body, or throws with the API's error message on a non-2xx.
127
- async mutate(method, path, body = {}) {
127
+ async mutate(method, path, body = {}, opts = {}) {
128
128
  const url = new URL(this.baseUrl + path);
129
+ const headers = {
130
+ Authorization: `Bearer ${this.token}`,
131
+ Accept: "application/json",
132
+ "Content-Type": "application/json",
133
+ "User-Agent": USER_AGENT,
134
+ };
135
+ // Idempotency-Key: the API replays the original response for a retried
136
+ // write instead of posting twice (see the API tokens help).
137
+ if (opts.idempotencyKey)
138
+ headers["Idempotency-Key"] = String(opts.idempotencyKey).slice(0, 255);
129
139
  let res;
130
140
  try {
131
141
  res = await fetch(url, {
132
142
  method,
133
- headers: {
134
- Authorization: `Bearer ${this.token}`,
135
- Accept: "application/json",
136
- "Content-Type": "application/json",
137
- "User-Agent": USER_AGENT,
138
- },
143
+ headers,
139
144
  body: JSON.stringify(body),
140
145
  });
141
146
  }
package/dist/tools.js CHANGED
@@ -38,7 +38,26 @@ const DATE_PROPS = {
38
38
  },
39
39
  to: { type: "string", description: "Latest document date (sent_on). Same format as `from`." },
40
40
  };
41
+ // Optional on every create/apply tool: reuse the same key when retrying a call
42
+ // that may have gone through (timeout, unclear error) and the API returns the
43
+ // original result instead of creating a second record.
44
+ const IDEMPOTENCY_PROP = {
45
+ idempotency_key: {
46
+ type: "string",
47
+ description: "Optional. A unique string (e.g. a UUID) for this operation. If you retry the same call after a timeout or an unclear error, pass the SAME key and Beeswax returns the original result instead of creating a duplicate. Use a new key for a genuinely new record.",
48
+ },
49
+ };
50
+ // Incremental sync: every listing accepts updated_since (ISO 8601) and the
51
+ // API echoes meta.filter.updated_since + meta.server_time.
52
+ const SYNC_PROPS = {
53
+ updated_since: {
54
+ type: "string",
55
+ description: "Incremental sync: only rows updated on or after this ISO 8601 instant (e.g. 2026-09-05T00:00:00Z). " +
56
+ "Line and contact-person edits touch their parent, so a changed line surfaces the document.",
57
+ },
58
+ };
41
59
  const ACCOUNTING_LIST_PROPS = {
60
+ ...SYNC_PROPS,
42
61
  state: { type: "string", description: "Filter by state (draft, finalised, pdf_sent, partial_paid, paid, …)." },
43
62
  project_id: { type: "integer", description: "Filter to a single project." },
44
63
  company_id: { type: "integer", description: "Filter to a single client/supplier company by id." },
@@ -98,6 +117,7 @@ function accountingTools(resource, singular, label, listName, opts = {}) {
98
117
  (payable ? " Pass overdue:true to get just the overdue ones (with due dates) in a single call." : ""),
99
118
  inputSchema: { type: "object", properties: { ...listProps, ...POSTED_PROPS } },
100
119
  handler: (client, args) => client.fetchAll(`/${resource}`, resource, {
120
+ updated_since: args.updated_since,
101
121
  posted: args.posted,
102
122
  include_drafts: args.include_drafts,
103
123
  state: args.state,
@@ -182,7 +202,7 @@ function ledgerDocumentTools(resource) {
182
202
  `Requires ${scope}.`,
183
203
  inputSchema: {
184
204
  type: "object",
185
- properties: {
205
+ properties: { ...IDEMPOTENCY_PROP,
186
206
  project_id: { type: "integer", description: "Project the document belongs to. Required." },
187
207
  ...partyProp,
188
208
  title: { type: "string" },
@@ -230,7 +250,7 @@ function ledgerDocumentTools(resource) {
230
250
  default_tax_id: args.default_tax_id,
231
251
  groups: (args.groups ?? []).map((g) => ({ ...g, due_on: normalizeDate(g.due_on) })),
232
252
  },
233
- }),
253
+ }, { idempotencyKey: args.idempotency_key }),
234
254
  },
235
255
  {
236
256
  name: `update_${singular}`,
@@ -411,7 +431,7 @@ const PAYMENT_TOOLS = [
411
431
  "There is no edit: remove a wrong payment (remove_payment) and apply it again. Confirm amounts with the user first. Requires payments:write.",
412
432
  inputSchema: {
413
433
  type: "object",
414
- properties: {
434
+ properties: { ...IDEMPOTENCY_PROP,
415
435
  bank_account_id: { type: "integer", description: "A transaction account flagged as a bank account." },
416
436
  paid_on: { type: "string", description: "Payment date, e.g. '2026-09-04'." },
417
437
  allocations: {
@@ -431,7 +451,7 @@ const PAYMENT_TOOLS = [
431
451
  },
432
452
  handler: (client, args) => client.mutate("POST", "/payments", {
433
453
  payment: { bank_account_id: args.bank_account_id, paid_on: normalizeDate(args.paid_on), allocations: args.allocations },
434
- }),
454
+ }, { idempotencyKey: args.idempotency_key }),
435
455
  },
436
456
  {
437
457
  name: "remove_payment",
@@ -553,13 +573,13 @@ export const TOOLS = [
553
573
  "Long-tail types (credits, bank_transfers, payrolls) and bare listings require an `all`-scoped token.",
554
574
  inputSchema: {
555
575
  type: "object",
556
- properties: {
576
+ properties: { ...SYNC_PROPS,
557
577
  type: { type: "string", description: "STI type filter, e.g. invoices, payments, payrolls, credits, bank_transfers." },
558
578
  state: { type: "string", description: "Filter by state (switches the posted default off)." },
559
579
  ...POSTED_PROPS,
560
580
  },
561
581
  },
562
- handler: (client, args) => client.fetchAll("/journal_entries", "journal_entries", {
582
+ handler: (client, args) => client.fetchAll("/journal_entries", "journal_entries", { updated_since: args.updated_since,
563
583
  type: args.type, state: args.state, posted: args.posted, include_drafts: args.include_drafts,
564
584
  }),
565
585
  },
@@ -583,12 +603,12 @@ export const TOOLS = [
583
603
  description: "List all time entries for the configured account (every page).",
584
604
  inputSchema: {
585
605
  type: "object",
586
- properties: {
606
+ properties: { ...SYNC_PROPS,
587
607
  project_id: { type: "integer", description: "Filter to a single project." },
588
608
  billable: { type: "boolean", description: "Filter by billable flag." },
589
609
  },
590
610
  },
591
- handler: (client, args) => client.fetchAll("/time_entries", "time_entries", { project_id: args.project_id, billable: args.billable }),
611
+ handler: (client, args) => client.fetchAll("/time_entries", "time_entries", { updated_since: args.updated_since, project_id: args.project_id, billable: args.billable }),
592
612
  },
593
613
  {
594
614
  name: "get_time_entry",
@@ -601,9 +621,9 @@ export const TOOLS = [
601
621
  description: "List calendar events visible to the token's user (private events the user is not invited to are excluded).",
602
622
  inputSchema: {
603
623
  type: "object",
604
- properties: { project_id: { type: "integer", description: "Filter to a single project." }, ...DATE_PROPS },
624
+ properties: { ...SYNC_PROPS, project_id: { type: "integer", description: "Filter to a single project." }, ...DATE_PROPS },
605
625
  },
606
- handler: (client, args) => client.fetchAll("/events", "events", {
626
+ handler: (client, args) => client.fetchAll("/events", "events", { updated_since: args.updated_since,
607
627
  project_id: args.project_id,
608
628
  from: normalizeDate(args.from),
609
629
  to: normalizeDate(args.to),
@@ -620,12 +640,12 @@ export const TOOLS = [
620
640
  description: "List all milestones (project TaskLists) for the configured account.",
621
641
  inputSchema: {
622
642
  type: "object",
623
- properties: {
643
+ properties: { ...SYNC_PROPS,
624
644
  project_id: { type: "integer", description: "Filter to a single project." },
625
645
  complete: { type: "boolean", description: "Filter by completion." },
626
646
  },
627
647
  },
628
- handler: (client, args) => client.fetchAll("/milestones", "milestones", { project_id: args.project_id, complete: args.complete }),
648
+ handler: (client, args) => client.fetchAll("/milestones", "milestones", { updated_since: args.updated_since, project_id: args.project_id, complete: args.complete }),
629
649
  },
630
650
  {
631
651
  name: "get_milestone",
@@ -636,8 +656,8 @@ export const TOOLS = [
636
656
  {
637
657
  name: "list_projects",
638
658
  description: "List the account's active projects. Use this to resolve a project name to the project_id that create_quote and the other document tools need.",
639
- inputSchema: { type: "object", properties: {} },
640
- handler: (client) => client.getList("/active_account/projects", "projects"),
659
+ inputSchema: { type: "object", properties: { ...SYNC_PROPS } },
660
+ handler: (client, args) => client.getList("/active_account/projects", "projects", { updated_since: args.updated_since }),
641
661
  },
642
662
  {
643
663
  name: "get_project",
@@ -668,12 +688,12 @@ export const TOOLS = [
668
688
  description: "List project documents. Freeform documents are markdown written in Beeswax's document editor — proposals, briefs, meeting notes — styled by the account's invoice theme; other document_types are generated from library templates and are read-only via this API. Each row carries state (draft/published/finalized/archived), current_version_number, and editing_lock (who has it open in the web editor right now). Filter with project_id and/or document_type (e.g. 'freeform').",
669
689
  inputSchema: {
670
690
  type: "object",
671
- properties: {
691
+ properties: { ...SYNC_PROPS,
672
692
  project_id: { type: "integer", description: "Only documents on this project" },
673
693
  document_type: { type: "string", description: "e.g. 'freeform' for markdown documents" },
674
694
  },
675
695
  },
676
- handler: (client, args) => client.fetchAll("/project_documents", "project_documents", {
696
+ handler: (client, args) => client.fetchAll("/project_documents", "project_documents", { updated_since: args.updated_since,
677
697
  project_id: args.project_id,
678
698
  document_type: args.document_type,
679
699
  }),
@@ -689,7 +709,7 @@ export const TOOLS = [
689
709
  description: "Create a freeform (markdown) document on a project. `body` is markdown (headings, lists, tables, blockquotes all render through the account's invoice theme). The optional `note` becomes the first version's 'what changed' line. Requires a token with the `project_documents:write` scope.",
690
710
  inputSchema: {
691
711
  type: "object",
692
- properties: {
712
+ properties: { ...IDEMPOTENCY_PROP,
693
713
  project_id: { type: "integer" },
694
714
  name: { type: "string", description: "Document title (defaults to 'Untitled document')" },
695
715
  body: { type: "string", description: "Markdown content" },
@@ -699,7 +719,7 @@ export const TOOLS = [
699
719
  },
700
720
  handler: (client, args) => client.mutate("POST", "/project_documents", {
701
721
  project_document: { project_id: args.project_id, name: args.name, body: args.body, note: args.note },
702
- }),
722
+ }, { idempotencyKey: args.idempotency_key }),
703
723
  },
704
724
  {
705
725
  name: "update_project_document",
@@ -829,7 +849,7 @@ export const TOOLS = [
829
849
  description: "List the account's products & services catalogue (stored as 'transaction templates'): everything the business sells or buys as a line item — products and services/activities with their unit, sell/buy prices, linked income/expense accounts and taxes. Use this whenever the user asks about products, services, price lists, rates or catalogue items.",
830
850
  inputSchema: {
831
851
  type: "object",
832
- properties: {
852
+ properties: { ...SYNC_PROPS,
833
853
  kind: {
834
854
  type: "string",
835
855
  enum: ["sell", "buy", "buy_and_sell", "product", "service"],
@@ -849,7 +869,7 @@ export const TOOLS = [
849
869
  },
850
870
  },
851
871
  },
852
- handler: (client, args) => client.fetchAll("/transaction_templates", "transaction_templates", {
872
+ handler: (client, args) => client.fetchAll("/transaction_templates", "transaction_templates", { updated_since: args.updated_since,
853
873
  kind: args.kind,
854
874
  side: args.side,
855
875
  active: args.active,
@@ -876,7 +896,7 @@ export const TOOLS = [
876
896
  "Titles must be unique within the account, and prices default to 0 if you omit them, so pass them explicitly. Requires a token with transaction_templates:write.",
877
897
  inputSchema: {
878
898
  type: "object",
879
- properties: {
899
+ properties: { ...IDEMPOTENCY_PROP,
880
900
  title: { type: "string", description: "Catalogue name, e.g. 'Shaker Door — Painted'. Must be unique in the account." },
881
901
  kind: {
882
902
  type: "string",
@@ -930,7 +950,7 @@ export const TOOLS = [
930
950
  physical_resource: args.physical_resource,
931
951
  gantt_color: args.gantt_color,
932
952
  },
933
- }),
953
+ }, { idempotencyKey: args.idempotency_key }),
934
954
  },
935
955
  {
936
956
  name: "update_product_service",
@@ -999,7 +1019,7 @@ export const TOOLS = [
999
1019
  // ── Tax returns (Australian company / sole-trader) ──────────────────
1000
1020
  {
1001
1021
  name: "list_tax_returns",
1002
- description: "List the account's tax returns (most recent financial year first) with status, completion %, and pending-findings count. Australian accounts only.",
1022
+ description: "List the account's tax returns (most recent financial year first) with status and completion %. Australian accounts only.",
1003
1023
  inputSchema: { type: "object", properties: {} },
1004
1024
  handler: (client) => client.getList("/tax_returns", "tax_returns"),
1005
1025
  },
@@ -1027,22 +1047,9 @@ export const TOOLS = [
1027
1047
  },
1028
1048
  handler: (client, args) => client.getOne(`/tax_returns/${args.id}/sections/${args.section_key}/fields/${args.field_key}/transactions`, ""),
1029
1049
  },
1030
- {
1031
- name: "list_tax_return_findings",
1032
- description: "List the AI review findings for a tax return. Optionally filter by status (pending, accepted, rejected, deferred, stale).",
1033
- inputSchema: {
1034
- type: "object",
1035
- properties: {
1036
- id: { type: "integer", description: "Tax return id." },
1037
- status: { type: "string", description: "Optional status filter." },
1038
- },
1039
- required: ["id"],
1040
- },
1041
- handler: (client, args) => client.getList(`/tax_returns/${args.id}/findings`, "findings", { status: args.status }),
1042
- },
1043
1050
  {
1044
1051
  name: "update_tax_return_field",
1045
- description: "Set a manual override on one tax-return field — the same as a person editing it in the web form. Recomputes the totals and marks any now-stale AI findings. Pass an empty value to clear an override and revert to the auto-filled figure. Fails on calculated/read-only fields and on finalised or lodged returns. Requires a token with the tax_returns:write scope.",
1052
+ description: "Set a manual override on one tax-return field — the same as a person editing it in the web form. Recomputes the totals. Pass an empty value to clear an override and revert to the auto-filled figure. Fails on calculated/read-only fields and on finalised or lodged returns. Requires a token with the tax_returns:write scope.",
1046
1053
  inputSchema: {
1047
1054
  type: "object",
1048
1055
  properties: {
@@ -1126,32 +1133,6 @@ export const TOOLS = [
1126
1133
  },
1127
1134
  handler: (client, args) => client.mutate("POST", `/tax_returns/${args.id}/repopulate`, {}),
1128
1135
  },
1129
- {
1130
- name: "request_tax_return_ai_review",
1131
- description: "Start an AI review of the tax return. The review runs in the background — poll get_tax_return (latest_review) or list_tax_return_findings to see the outcome. Requires a token with the tax_returns:write scope.",
1132
- inputSchema: {
1133
- type: "object",
1134
- properties: { id: { type: "integer", description: "Tax return id." } },
1135
- required: ["id"],
1136
- },
1137
- handler: (client, args) => client.mutate("POST", `/tax_returns/${args.id}/review`, {}),
1138
- },
1139
- {
1140
- name: "resolve_tax_return_finding",
1141
- description: "Resolve one pending AI review finding: accept (applies the suggested value to the field), reject, or defer. Every pending finding in a section must be resolved before that section can be confirmed. Requires a token with the tax_returns:write scope.",
1142
- inputSchema: {
1143
- type: "object",
1144
- properties: {
1145
- id: { type: "integer", description: "Tax return id." },
1146
- finding_id: { type: "integer", description: "Finding id (from list_tax_return_findings)." },
1147
- resolution: { type: "string", enum: ["accept", "reject", "defer"], description: "How to resolve the finding." },
1148
- },
1149
- required: ["id", "finding_id", "resolution"],
1150
- },
1151
- handler: (client, args) => client.mutate("POST", `/tax_returns/${args.id}/findings/${args.finding_id}/resolve`, {
1152
- resolution: args.resolution,
1153
- }),
1154
- },
1155
1136
  {
1156
1137
  name: "finalise_tax_return",
1157
1138
  description: "Finalise a tax return once every section is confirmed. A finalised return is locked against edits (use unfinalise_tax_return to unlock it). This does NOT lodge the return with the ATO. Requires a token with the tax_returns:write scope.",
@@ -1266,12 +1247,12 @@ export const TOOLS = [
1266
1247
  "Requires journal_entries:read (write implies read).",
1267
1248
  inputSchema: {
1268
1249
  type: "object",
1269
- properties: {
1250
+ properties: { ...SYNC_PROPS,
1270
1251
  state: { type: "string", enum: ["draft", "finalised"], description: "Optional state filter (switches the posted default off)." },
1271
1252
  ...POSTED_PROPS,
1272
1253
  },
1273
1254
  },
1274
- handler: (client, args) => client.fetchAll("/raw_journal_entries", "raw_journal_entries", {
1255
+ handler: (client, args) => client.fetchAll("/raw_journal_entries", "raw_journal_entries", { updated_since: args.updated_since,
1275
1256
  state: args.state, posted: args.posted, include_drafts: args.include_drafts,
1276
1257
  }),
1277
1258
  },
@@ -1293,9 +1274,9 @@ export const TOOLS = [
1293
1274
  "Requires a token with the journal_entries:write scope.",
1294
1275
  inputSchema: {
1295
1276
  type: "object",
1296
- properties: {
1277
+ properties: { ...IDEMPOTENCY_PROP,
1297
1278
  date: { type: "string", description: "Entry date, e.g. '2024-04-01'." },
1298
- narration: { type: "string", description: "What the journal is for, e.g. 'BMW write-off'." },
1279
+ narration: { type: "string", maxLength: 255, description: "What the journal is for, e.g. 'BMW write-off'. At most 255 characters — put detail in the line descriptions." },
1299
1280
  status: { type: "string", enum: ["finalised", "draft"], description: "Optional. Default 'finalised' posts to the ledger immediately; 'draft' stages it for review." },
1300
1281
  lines: {
1301
1282
  type: "array",
@@ -1321,7 +1302,7 @@ export const TOOLS = [
1321
1302
  status: args.status,
1322
1303
  lines: args.lines,
1323
1304
  },
1324
- }),
1305
+ }, { idempotencyKey: args.idempotency_key }),
1325
1306
  },
1326
1307
  {
1327
1308
  name: "finalise_manual_journal",
@@ -1344,7 +1325,7 @@ export const TOOLS = [
1344
1325
  "Requires a token with transaction_accounts:read.",
1345
1326
  inputSchema: {
1346
1327
  type: "object",
1347
- properties: {
1328
+ properties: { ...SYNC_PROPS,
1348
1329
  account_type: {
1349
1330
  type: "string",
1350
1331
  enum: ["income", "expense", "asset", "loan", "equity", "transfer"],
@@ -1354,7 +1335,7 @@ export const TOOLS = [
1354
1335
  bank: { type: "boolean", description: "true returns only bank accounts." },
1355
1336
  },
1356
1337
  },
1357
- handler: (client, args) => client.getList("/active_account/transaction_accounts", "transaction_accounts", {
1338
+ handler: (client, args) => client.getList("/active_account/transaction_accounts", "transaction_accounts", { updated_since: args.updated_since,
1358
1339
  account_type: args.account_type,
1359
1340
  active: args.active,
1360
1341
  bank: args.bank,
@@ -1404,12 +1385,12 @@ export const TOOLS = [
1404
1385
  "Fetches every page. Requires a token with companies:read.",
1405
1386
  inputSchema: {
1406
1387
  type: "object",
1407
- properties: {
1388
+ properties: { ...SYNC_PROPS,
1408
1389
  role: { type: "string", enum: ["client", "supplier"], description: "Filter to one role. A quote is addressed to a client." },
1409
1390
  name: { type: "string", description: "Case-insensitive substring match on the company name." },
1410
1391
  },
1411
1392
  },
1412
- handler: (client, args) => client.fetchAll("/active_account/companies", "companies", { role: args.role, name: args.name }),
1393
+ handler: (client, args) => client.fetchAll("/active_account/companies", "companies", { updated_since: args.updated_since, role: args.role, name: args.name }),
1413
1394
  },
1414
1395
  {
1415
1396
  name: "get_company",
@@ -1424,7 +1405,7 @@ export const TOOLS = [
1424
1405
  "Check list_companies first — names are unique per account and a duplicate is refused. Nothing here emails anyone. Requires companies:write.",
1425
1406
  inputSchema: {
1426
1407
  type: "object",
1427
- properties: {
1408
+ properties: { ...IDEMPOTENCY_PROP,
1428
1409
  name: { type: "string" },
1429
1410
  client: { type: "boolean", description: "You sell to them." },
1430
1411
  supplier: { type: "boolean", description: "You buy from them." },
@@ -1451,7 +1432,7 @@ export const TOOLS = [
1451
1432
  },
1452
1433
  required: ["name"],
1453
1434
  },
1454
- handler: (client, args) => client.mutate("POST", "/active_account/companies", { company: args }),
1435
+ handler: (client, args) => client.mutate("POST", "/active_account/companies", { company: args }, { idempotencyKey: args.idempotency_key }),
1455
1436
  },
1456
1437
  {
1457
1438
  name: "update_company",
@@ -1508,7 +1489,7 @@ export const TOOLS = [
1508
1489
  "Requires a token with the quotes:write scope.",
1509
1490
  inputSchema: {
1510
1491
  type: "object",
1511
- properties: {
1492
+ properties: { ...IDEMPOTENCY_PROP,
1512
1493
  project_id: { type: "integer", description: "Project the quote belongs to. Required." },
1513
1494
  company_id: { type: "integer", description: "Client the quote is addressed to. Required — see list_companies." },
1514
1495
  title: { type: "string", description: "Quote title, e.g. 'Cambridge Market Admin App Website'." },
@@ -1574,7 +1555,7 @@ export const TOOLS = [
1574
1555
  start_on: normalizeDate(group.start_on),
1575
1556
  })),
1576
1557
  },
1577
- }),
1558
+ }, { idempotencyKey: args.idempotency_key }),
1578
1559
  },
1579
1560
  {
1580
1561
  name: "update_quote",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beeswax-mcp",
3
- "version": "1.6.0",
3
+ "version": "2.0.0",
4
4
  "description": "Official MCP server for Beeswax (beeswaxapp.com) — query invoices, quotes, expenses, payments, journals, time entries, projects, products & services and tax returns from Claude and other MCP clients, and build quotes priced from your catalogue.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.beeswaxapp.com/support/mcp-setup",