@countfinancial/cli 0.1.8 → 0.2.2

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 (105) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +9 -1
  3. package/dist/__tests__/credentialStore.test.js +9 -0
  4. package/dist/__tests__/credentialStore.test.js.map +1 -1
  5. package/dist/__tests__/doctor.test.d.ts +1 -0
  6. package/dist/__tests__/doctor.test.js +30 -0
  7. package/dist/__tests__/doctor.test.js.map +1 -0
  8. package/dist/__tests__/mcpInstall.test.d.ts +1 -0
  9. package/dist/__tests__/mcpInstall.test.js +25 -0
  10. package/dist/__tests__/mcpInstall.test.js.map +1 -0
  11. package/dist/__tests__/profileStore.test.d.ts +1 -0
  12. package/dist/__tests__/profileStore.test.js +53 -0
  13. package/dist/__tests__/profileStore.test.js.map +1 -0
  14. package/dist/cli.js +122 -9
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/doctor.command.d.ts +6 -0
  17. package/dist/commands/doctor.command.js +12 -0
  18. package/dist/commands/doctor.command.js.map +1 -0
  19. package/dist/commands/init.command.d.ts +1 -0
  20. package/dist/commands/init.command.js +6 -1
  21. package/dist/commands/init.command.js.map +1 -1
  22. package/dist/commands/login.command.d.ts +1 -0
  23. package/dist/commands/login.command.js +3 -3
  24. package/dist/commands/login.command.js.map +1 -1
  25. package/dist/commands/logout.command.d.ts +5 -1
  26. package/dist/commands/logout.command.js +3 -3
  27. package/dist/commands/logout.command.js.map +1 -1
  28. package/dist/commands/mcp.command.d.ts +5 -1
  29. package/dist/commands/mcp.command.js +7 -4
  30. package/dist/commands/mcp.command.js.map +1 -1
  31. package/dist/commands/mcpInstall.command.d.ts +7 -0
  32. package/dist/commands/mcpInstall.command.js +9 -0
  33. package/dist/commands/mcpInstall.command.js.map +1 -0
  34. package/dist/commands/profiles.command.d.ts +17 -0
  35. package/dist/commands/profiles.command.js +48 -0
  36. package/dist/commands/profiles.command.js.map +1 -0
  37. package/dist/commands/setup.command.d.ts +11 -0
  38. package/dist/commands/setup.command.js +85 -0
  39. package/dist/commands/setup.command.js.map +1 -0
  40. package/dist/commands/status.command.d.ts +6 -1
  41. package/dist/commands/status.command.js +7 -4
  42. package/dist/commands/status.command.js.map +1 -1
  43. package/dist/commands/testTools.command.d.ts +7 -0
  44. package/dist/commands/testTools.command.js +42 -0
  45. package/dist/commands/testTools.command.js.map +1 -0
  46. package/dist/constants.d.ts +2 -2
  47. package/dist/constants.js +2 -2
  48. package/dist/constants.js.map +1 -1
  49. package/dist/helpers/toolSmokeTestFixtures.helper.d.ts +16 -0
  50. package/dist/helpers/toolSmokeTestFixtures.helper.js +491 -0
  51. package/dist/helpers/toolSmokeTestFixtures.helper.js.map +1 -0
  52. package/dist/partner-mcp/helpers/mcpKnowledge.helper.js +114 -4
  53. package/dist/partner-mcp/helpers/mcpKnowledge.helper.js.map +1 -1
  54. package/dist/partner-mcp/helpers/mcpPayloadValidation.helper.d.ts +1 -1
  55. package/dist/partner-mcp/helpers/mcpPayloadValidation.helper.js +61 -84
  56. package/dist/partner-mcp/helpers/mcpPayloadValidation.helper.js.map +1 -1
  57. package/dist/partner-mcp/helpers/mcpPlaybooks.helper.js +181 -2
  58. package/dist/partner-mcp/helpers/mcpPlaybooks.helper.js.map +1 -1
  59. package/dist/partner-mcp/helpers/mcpRecoveryHint.helper.js +79 -0
  60. package/dist/partner-mcp/helpers/mcpRecoveryHint.helper.js.map +1 -1
  61. package/dist/partner-mcp/schemas/bodies.d.ts +528 -0
  62. package/dist/partner-mcp/schemas/bodies.js +377 -0
  63. package/dist/partner-mcp/schemas/bodies.js.map +1 -0
  64. package/dist/partner-mcp/schemas/builders.d.ts +36 -0
  65. package/dist/partner-mcp/schemas/builders.js +106 -0
  66. package/dist/partner-mcp/schemas/builders.js.map +1 -0
  67. package/dist/partner-mcp/schemas/index.d.ts +5 -0
  68. package/dist/partner-mcp/schemas/index.js +6 -0
  69. package/dist/partner-mcp/schemas/index.js.map +1 -0
  70. package/dist/partner-mcp/schemas/primitives.d.ts +39 -0
  71. package/dist/partner-mcp/schemas/primitives.js +107 -0
  72. package/dist/partner-mcp/schemas/primitives.js.map +1 -0
  73. package/dist/partner-mcp/schemas/queries.d.ts +311 -0
  74. package/dist/partner-mcp/schemas/queries.js +175 -0
  75. package/dist/partner-mcp/schemas/queries.js.map +1 -0
  76. package/dist/partner-mcp/schemas/toolInputSchemas.d.ts +23 -0
  77. package/dist/partner-mcp/schemas/toolInputSchemas.js +248 -0
  78. package/dist/partner-mcp/schemas/toolInputSchemas.js.map +1 -0
  79. package/dist/partner-mcp/tools/definitions.js +26 -135
  80. package/dist/partner-mcp/tools/definitions.js.map +1 -1
  81. package/dist/partner-mcp/tools/registerTools.d.ts +8 -0
  82. package/dist/partner-mcp/tools/registerTools.js +8 -3
  83. package/dist/partner-mcp/tools/registerTools.js.map +1 -1
  84. package/dist/services/credentialStore.service.d.ts +8 -2
  85. package/dist/services/credentialStore.service.js +22 -10
  86. package/dist/services/credentialStore.service.js.map +1 -1
  87. package/dist/services/doctor.service.d.ts +22 -0
  88. package/dist/services/doctor.service.js +186 -0
  89. package/dist/services/doctor.service.js.map +1 -0
  90. package/dist/services/mcpInstall.service.d.ts +43 -0
  91. package/dist/services/mcpInstall.service.js +117 -0
  92. package/dist/services/mcpInstall.service.js.map +1 -0
  93. package/dist/services/mcpLauncher.service.d.ts +2 -0
  94. package/dist/services/mcpLauncher.service.js +6 -3
  95. package/dist/services/mcpLauncher.service.js.map +1 -1
  96. package/dist/services/profileStore.service.d.ts +53 -0
  97. package/dist/services/profileStore.service.js +129 -0
  98. package/dist/services/profileStore.service.js.map +1 -0
  99. package/dist/services/setupPrompt.service.d.ts +25 -0
  100. package/dist/services/setupPrompt.service.js +40 -0
  101. package/dist/services/setupPrompt.service.js.map +1 -0
  102. package/dist/services/toolSmokeTest.service.d.ts +38 -0
  103. package/dist/services/toolSmokeTest.service.js +495 -0
  104. package/dist/services/toolSmokeTest.service.js.map +1 -0
  105. package/package.json +3 -2
@@ -1,12 +1,11 @@
1
- import { bodyInputSchema, describeEndpointInputSchema, emptyInputSchema, idBodyInputSchema, idInputSchema, idOptionalBodyInputSchema, idVersionNumberBodyInputSchema, knowledgeInputSchema, playbooksInputSchema, queryInputSchema, resolveReferencesInputSchema, validatePayloadInputSchema, } from './schemas.js';
2
- const tools = [
1
+ import * as toolInputSchemas from '../schemas/toolInputSchemas.js';
2
+ const rawTools = [
3
3
  {
4
4
  name: 'COUNT_auth_status',
5
5
  title: 'COUNT Auth Status',
6
6
  description: 'Return the MCP server authentication configuration state without exposing secrets.',
7
7
  method: 'GET',
8
8
  pathTemplate: '/__local/auth-status',
9
- inputSchema: emptyInputSchema,
10
9
  requiresUserAuth: false,
11
10
  readOnly: true,
12
11
  destructive: false,
@@ -17,7 +16,6 @@ const tools = [
17
16
  description: 'Return human-readable guidance for a specific COUNT tool, including the partner API path it wraps, expected query/body fields from the public COUNT developer documentation, and tips for which IDs to pass. Useful for checking request shape before unfamiliar create/update operations.',
18
17
  method: 'GET',
19
18
  pathTemplate: '/__local/describe-endpoint',
20
- inputSchema: describeEndpointInputSchema,
21
19
  requiresUserAuth: false,
22
20
  readOnly: true,
23
21
  destructive: false,
@@ -28,7 +26,6 @@ const tools = [
28
26
  description: 'COUNT connector and workflow FAQ — call this for “how do I…?” questions before guessing. Covers: authorizing additional workspaces (disconnect + reconnect), Claude/ChatGPT reconnect steps, multiple workspace usage, why auth_status may show fewer workspaces than the COUNT web app, external UUID conventions, P&L category filters (e.g. shipping/Freight & Courier), and bulk import batch sizes. Optional input: `topic` (FAQ id) or `search` (free text). Omit both to list all topics with summaries.',
29
27
  method: 'GET',
30
28
  pathTemplate: '/__local/knowledge',
31
- inputSchema: knowledgeInputSchema,
32
29
  requiresUserAuth: false,
33
30
  readOnly: true,
34
31
  destructive: false,
@@ -36,10 +33,9 @@ const tools = [
36
33
  {
37
34
  name: 'COUNT_playbooks',
38
35
  title: 'COUNT Playbooks',
39
- description: 'End-to-end accounting workflows with ordered steps and tool names. Call before multi-step tasks such as paying a vendor bill, bulk migration imports, or month-end review. Optional input: `playbook` (id) or `search` (free text). Omit both to list all playbooks with summaries. Playbook ids: pay_vendor_bill, migration_import, month_end_review.',
36
+ description: 'End-to-end accounting workflows with ordered steps and tool names. Call before multi-step tasks such as paying a vendor bill, creating and sending an invoice, bulk migration imports, chart-of-accounts setup with transaction import, budget planning with actuals review, or month-end review. Optional input: `playbook` (id) or `search` (free text). Omit both to list all playbooks with summaries. Playbook ids: pay_vendor_bill, create_invoice_and_send, migration_import, budget_import, month_end_review, setup_accounts_and_import_transactions, plan_budget_and_review_actuals.',
40
37
  method: 'GET',
41
38
  pathTemplate: '/__local/playbooks',
42
- inputSchema: playbooksInputSchema,
43
39
  requiresUserAuth: false,
44
40
  readOnly: true,
45
41
  destructive: false,
@@ -50,7 +46,6 @@ const tools = [
50
46
  description: 'Resolve human-readable vendor, customer, account, project, or tag names to external COUNT UUIDs for this workspace. Pass one or more name fields; each returns uuid, matchConfidence (exact/fuzzy/ambiguous/not_found), and candidates when ambiguous. Use before bulk imports instead of guessing UUIDs from large list results.',
51
47
  method: 'GET',
52
48
  pathTemplate: '/__local/resolve-references',
53
- inputSchema: resolveReferencesInputSchema,
54
49
  requiresUserAuth: false,
55
50
  readOnly: true,
56
51
  destructive: false,
@@ -58,10 +53,9 @@ const tools = [
58
53
  {
59
54
  name: 'COUNT_validate_payload',
60
55
  title: 'Validate Payload',
61
- description: 'Preflight validation for mutation tool payloads — no data is written. Checks bulk batch size (cap 100), legacy numeric field rejection, and required fields for bulk create and create_bill. Set verifyReferences: true to confirm UUIDs exist in the workspace. Call before COUNT_bulk_create_* imports.',
56
+ description: 'Preflight validation for partner API tool payloads — no data is written. Runs typed schema validation on query and body, bulk batch size (cap 100), legacy numeric field rejection, and required fields for bulk create and create_bill. Set verifyReferences: true to confirm UUIDs exist in the workspace. Works for list and mutation tools.',
62
57
  method: 'GET',
63
58
  pathTemplate: '/__local/validate-payload',
64
- inputSchema: validatePayloadInputSchema,
65
59
  requiresUserAuth: false,
66
60
  readOnly: true,
67
61
  destructive: false,
@@ -72,7 +66,6 @@ const tools = [
72
66
  description: 'Refresh the COUNT partner access token using the configured refresh token. The refreshed token is kept in this MCP process memory.',
73
67
  method: 'POST',
74
68
  pathTemplate: '/partners/refresh-user-access-token',
75
- inputSchema: emptyInputSchema,
76
69
  requiresUserAuth: false,
77
70
  readOnly: false,
78
71
  destructive: false,
@@ -81,10 +74,9 @@ const tools = [
81
74
  {
82
75
  name: 'COUNT_list_transactions',
83
76
  title: 'List Transactions',
84
- description: 'List register/bank transactions for the authenticated workspace. Common `query` filters: `accUuid` (single bank/cash account UUID), `categoryAccountUuid`, `vendorUuid`, `customerUuid`, `projectUuid`, `tagUuids` (comma-separated), `startDate` / `endDate` (YYYY-MM-DD), `transactionTypes` ("Expense" | "Income" | "Transfer" | "Journal Entry"; `type` is also accepted as an alias), `reviewed: "true" | "false"`, `reconciled: "true" | "false"`, `search` (substring against description), `page`, and `limit` (default 50, server cap applies). Returns transaction rows with `id` exposed as the external UUID — pass that same UUID back to `update_transaction` / `change_transaction_category` / `delete_transaction`.',
77
+ description: 'List register/bank transactions for the authenticated workspace. Returns transaction rows with `id` as the external UUID — pass that UUID to update/change-category/delete tools. Filter fields are documented on the `query` input schema.',
85
78
  method: 'GET',
86
79
  pathTemplate: '/partners/transactions',
87
- inputSchema: queryInputSchema,
88
80
  requiresUserAuth: true,
89
81
  readOnly: true,
90
82
  destructive: false,
@@ -95,7 +87,6 @@ const tools = [
95
87
  description: 'Get a single transaction by external UUID. Returns transactions hidden from the workspace UI as well. Optional `query` fields are forwarded as Sequelize `include` directives to expand related entities (e.g. category, vendor, customer, tags, taxes). Use the returned `id` (the external UUID) as the next call argument for `update_transaction`, `change_transaction_category`, or `delete_transaction`.',
96
88
  method: 'GET',
97
89
  pathTemplate: '/partners/transactions/{id}',
98
- inputSchema: idInputSchema,
99
90
  requiresUserAuth: true,
100
91
  readOnly: true,
101
92
  destructive: false,
@@ -106,7 +97,6 @@ const tools = [
106
97
  description: 'Create a register/bank transaction in the authenticated COUNT workspace. **Set the category, vendor/customer, project, tags, and taxes in this single call — do NOT follow up with `change_transaction_category` when you already know the category at creation time.** The create endpoint resolves the category UUID, posts the journal entries, and writes tax associations atomically. Required body: `accUuid` (account UUID from `list_accounts` — the bank/cash account the money moves through), `amount` (positive decimal — see sign convention below), and either `postedDate` or `date` (YYYY-MM-DD). Optional body: `description`, `currency`, `type` (`Expense` | `Income` | `Transfer` | `Journal Entry` — case-sensitive, but lowercase `expense`/`income`/`transfer`/`journal`/`journal_entry` are also accepted and normalized server-side), `categoryAccountUuid` (income/expense category — same UUID source as `accUuid`; **set this here at creation, no separate `change_transaction_category` call needed**), `vendorUuid`, `customerUuid`, `projectUuid`, `tagUuids: string[]`, `taxes: number[]` (numeric internal tax ids — taxes are not yet UUID-resolved on the partner API), `notes`, `authorizedDate`. **Sign convention**: pass `amount` as a positive number; the server flips the sign for income internally. **Type omission**: when `type` is not sent, the server infers it from the category-account type. **FK resolution**: `vendorUuid`/`customerUuid`/`projectUuid`/`tagUuids` are resolved into the internal numeric ids the service expects; do not send `vendorId`/`customerId`/`projectId`/`tags` numeric arrays unless you really do have internal ids. **Use `change_transaction_category` only to modify the category on an EXISTING transaction.**',
107
98
  method: 'POST',
108
99
  pathTemplate: '/partners/transactions',
109
- inputSchema: bodyInputSchema,
110
100
  requiresUserAuth: true,
111
101
  readOnly: false,
112
102
  destructive: false,
@@ -117,7 +107,6 @@ const tools = [
117
107
  description: 'Create up to 100 register/bank transactions in a single call. Body: `{ "transactions": [<row>, ...] }` where each `<row>` uses the EXACT same shape as `COUNT_create_transaction` (required `accUuid`, `amount`, `postedDate`/`date`; optional `description`, `currency`, `type`, `categoryAccountUuid`, `vendorUuid`, `customerUuid`, `projectUuid`, `tagUuids: string[]`, `taxes: number[]`, `notes`, `authorizedDate`). **Set the category, vendor/customer, project, tags, and taxes ON EACH ROW in this single call — no follow-up `change_transaction_category` needed.** **Partial-success contract**: each row runs in its own DB transaction; one row failing never rolls back another row\'s writes. Response envelope: `{ "successCount": N, "errorCount": M, "results": [{ "index": 0, "success": true, "transaction": {...} } | { "index": 1, "success": false, "error": "..." }, ...] }` — read `errorCount` first; if non-zero, iterate `results` and only retry the rows where `success: false`. The HTTP status is always `201` when the batch was accepted, even if all rows failed. **Cap**: 100 rows per call (split larger imports into multiple calls). **Recommended batch size**: ~25 rows for large historical imports (e.g. billing-system backfills); larger batches increase timeout risk. **Use cases**: ingest a CSV of bank transactions, replay a missed sync window, backfill historical activity. For a single transaction, prefer `COUNT_create_transaction` — the response shape is simpler.',
118
108
  method: 'POST',
119
109
  pathTemplate: '/partners/transactions/bulk',
120
- inputSchema: bodyInputSchema,
121
110
  requiresUserAuth: true,
122
111
  readOnly: false,
123
112
  destructive: false,
@@ -128,7 +117,6 @@ const tools = [
128
117
  description: 'Update non-category fields on an EXISTING transaction by external UUID. Body fields: `vendorUuid`, `customerUuid`, `projectUuid`, `tagUuids: string[]` (replaces all tags — `[]` clears them), plus regular safe Transaction columns (`description`, `notes`, `amount`, `postedDate`, `authorizedDate`, etc.). Do not send `categoryAccountUuid` or `categoryAccountId` here; use `COUNT_change_transaction_category` to change/clear the category on an existing transaction so taxes, transfers, fixed assets, and journal side effects run through the accounting workflow. (When CREATING a new transaction, pass `categoryAccountUuid` directly to `COUNT_create_transaction` — no follow-up call is needed.) The response includes `_partnerWarnings: [{ field, reason, message }]` when fields you sent were ignored (`reason: "internal_only"` for fields managed internally — e.g. `linkedTransferTransactionId`, `providerTransactionId`, `billId`; `reason: "unknown_field"` for typos / fields not on the transaction model).',
129
118
  method: 'PATCH',
130
119
  pathTemplate: '/partners/transactions/{id}',
131
- inputSchema: idBodyInputSchema,
132
120
  requiresUserAuth: true,
133
121
  readOnly: false,
134
122
  destructive: false,
@@ -139,7 +127,6 @@ const tools = [
139
127
  description: 'Change, clear, or transfer-link the category (GL account) on an EXISTING transaction by external UUID. **Use this tool only when modifying the category of a transaction that already exists.** When creating a NEW transaction, pass `categoryAccountUuid` directly to `COUNT_create_transaction` instead — the create endpoint resolves the category, posts the journal entries, and writes tax associations in a single call (no follow-up `change_transaction_category` is needed). Body: `categoryAccountUuid` (the `id`, `accUuid`, or `accountUuid` of the target account from `list_accounts`); pass `null` to uncategorize. Optional body fields: `notes`, `taxes`, `autoCreateMatching`, `autoReview`, `linkedTransferTransactionUuid` (or UUID-shaped `linkedTransferTransactionId` from `list_transactions`), `fixedAssetAssignment`. The category-change workflow ensures taxes, transfers, fixed assets, and journal side effects all stay in sync on existing rows.',
140
128
  method: 'PATCH',
141
129
  pathTemplate: '/partners/transactions/{id}/change-category',
142
- inputSchema: idBodyInputSchema,
143
130
  requiresUserAuth: true,
144
131
  readOnly: false,
145
132
  destructive: false,
@@ -150,7 +137,6 @@ const tools = [
150
137
  description: 'Apply an existing bank/expense/income transaction as payment against one or more invoices or bills. URL `{id}` is the transaction UUID from `list_transactions`. Body: `{ matchingType: "invoice" | "bill", records: [{ id: <invoice or bill UUID>, paymentAmount: <decimal>, notes?: string }], withCaution?: boolean, parentCategoryAccountUuid?: string }`. For invoices use `matchingType: "invoice"` with an **Income** transaction (bank deposit); invoice refunds use **Expense**. For bills use `matchingType: "bill"` with an **Expense** transaction (vendor payment); vendor memos use **Income**. `paymentAmount` must not exceed the document open balance; partial payments may split the transaction. Optional `parentCategoryAccountUuid` sets the category for any remainder when the payment is less than the transaction amount. Do not set `invoiceId` / `billId` on transaction update — use this endpoint instead.',
151
138
  method: 'POST',
152
139
  pathTemplate: '/partners/transactions/{id}/assign-to-bills-invoices',
153
- inputSchema: idBodyInputSchema,
154
140
  requiresUserAuth: true,
155
141
  readOnly: false,
156
142
  destructive: false,
@@ -161,7 +147,6 @@ const tools = [
161
147
  description: 'Delete a transaction by external UUID.',
162
148
  method: 'DELETE',
163
149
  pathTemplate: '/partners/transactions/{id}',
164
- inputSchema: idInputSchema,
165
150
  requiresUserAuth: true,
166
151
  readOnly: false,
167
152
  destructive: true,
@@ -172,7 +157,6 @@ const tools = [
172
157
  description: 'List every chart-of-accounts sub-type available in COUNT (global catalog — the same integer ids apply in every workspace). **Call this before `COUNT_create_account`** to discover the correct `subTypeId`; e.g. set `query.type` to `"Liabilities"` and pick the row whose `name` is `Credit Card`. Optional `query.type`: one of `Assets`, `Liabilities`, `Equity`, `Income`, `Expenses` (case-sensitive, plural). Each row returns `id` (pass as `subTypeId` on create), `type`, `name`, `description`, and `anchorTier`. **Never guess or sequentially probe subType ids** — they are sparse and not predictable from other ids.',
173
158
  method: 'GET',
174
159
  pathTemplate: '/partners/account-sub-types',
175
- inputSchema: queryInputSchema,
176
160
  requiresUserAuth: true,
177
161
  readOnly: true,
178
162
  destructive: false,
@@ -180,10 +164,9 @@ const tools = [
180
164
  {
181
165
  name: 'COUNT_list_accounts',
182
166
  title: 'List Chart Of Accounts',
183
- description: 'List chart of accounts for the authenticated COUNT workspace. When you need a `subTypeId` for a **new** account and no similar account exists yet, prefer `COUNT_list_account_sub_types` instead of guessing from sparse ids. Optional filters (pass under `query`): `type` must be one of `Assets`, `Liabilities`, `Equity`, `Income`, `Expenses` (case-sensitive, plural — singular forms like "Asset" are rejected); `subTypeId` is a positive integer; `search` is a substring matched against account name / number; boolean-string filters `inactive`, `includeBalances`, `includeHidden`, `includeHiddenAccounts`, `is1099Box`, `notAssignedToReporter`, `onlyCategoryAccounts`, `includeDeleteMeta` accept `"true"` / `"false"`. Existing rows include `subType.id` — you may reuse that integer when creating another account in the same bucket.',
167
+ description: 'List chart of accounts for the authenticated COUNT workspace. When you need a `subTypeId` for a **new** account and no similar account exists yet, prefer `COUNT_list_account_sub_types` instead of guessing from sparse ids. Optional filters (pass under `query`): `type` must be one of `Assets`, `Liabilities`, `Equity`, `Income`, `Expenses` (case-sensitive, plural — singular forms like "Asset" are rejected); `subTypeId` is a positive integer; `search` is a substring matched against account name / number; boolean-string filters `inactive`, `includeBalances`, `includeHidden`, `includeHiddenAccounts`, `is1099Box`, `notAssignedToReporter`, `onlyCategoryAccounts`, `includeDeleteMeta` accept `"true"` / `"false"`. Each row includes `editable` (`false` for system/control/connected accounts). When `includeDeleteMeta=true`, each row also includes `canDelete` and `deleteBlockedReason` (`NOT_EDITABLE`, `HAS_JOURNAL_ENTRIES`, `HAS_SUB_ACCOUNTS`, `HAS_INVOICE_PRODUCTS`, `HAS_PAYROLL_MAPPINGS`) so you can tell whether update/delete will succeed before calling those tools. Existing rows include `subType.id` — you may reuse that integer when creating another account in the same bucket.',
184
168
  method: 'GET',
185
169
  pathTemplate: '/partners/chart-of-accounts',
186
- inputSchema: queryInputSchema,
187
170
  requiresUserAuth: true,
188
171
  readOnly: true,
189
172
  destructive: false,
@@ -194,7 +177,16 @@ const tools = [
194
177
  description: 'Create a chart-of-accounts entry. **Always resolve `subTypeId` first — never guess or probe ids.** Preferred: `COUNT_list_account_sub_types` (filter `query.type`, pick the matching `name`, use its `id`). Fallback: `COUNT_list_accounts` with `query.type` and reuse `subType.id` from an existing row in that bucket. Required body: `name` (string) and `subTypeId` (integer from list_account_sub_types or list_accounts). Optional body: `accountNumber`, `currency` (defaults to the workspace currency), `description`, `color` (hex), `parentAccountId` (numeric id of an existing parent account — sub-accounts inherit the parent\'s `type` / `currency` / `status` and a sub-account cannot itself have sub-accounts), `institutionId`, `taxes: number[]` (internal numeric tax ids — required for NZ workspaces), `status` ("active" | "inactive"). The high-level `type` (Assets/Liabilities/Equity/Income/Expenses) is derived from `subTypeId` server-side; do not send it. If creation fails, tell the user COUNT could not create the account and include the `requestId` when present — **never** quote stack traces, source file paths, line numbers, or internal database field names in user-visible text.',
195
178
  method: 'POST',
196
179
  pathTemplate: '/partners/chart-of-accounts',
197
- inputSchema: bodyInputSchema,
180
+ requiresUserAuth: true,
181
+ readOnly: false,
182
+ destructive: false,
183
+ },
184
+ {
185
+ name: 'COUNT_bulk_create_accounts',
186
+ title: 'Bulk Create Accounts',
187
+ description: 'Create up to 100 chart-of-accounts entries in a single call. Body: `{ "accounts": [<row>, ...] }` where each `<row>` uses the EXACT same shape as `COUNT_create_account` (required `name` and `subTypeId`; optional `accountNumber`, `currency`, `description`, `color`, `parentAccountId`, `institutionId`, `taxes`, `status`). **Partial-success contract**: each row runs in its own DB transaction; one row failing never rolls back another row\'s writes. Response envelope: `{ "successCount": N, "errorCount": M, "results": [{ "index": 0, "success": true, "account": {...} } | { "index": 1, "success": false, "error": "..." }, ...] }` — read `errorCount` first; if non-zero, iterate `results` and only retry the rows where `success: false`. The HTTP status is always `201` when the batch was accepted, even if all rows failed. **Cap**: 100 rows per call. **Recommended batch size**: ~25 rows for migrations. **Complete the full chart of accounts before importing bills or transactions** — accounts with journal entries cannot be deleted (`deleteBlockedReason: HAS_JOURNAL_ENTRIES`); use `COUNT_update_account` with `status: inactive` instead.',
188
+ method: 'POST',
189
+ pathTemplate: '/partners/chart-of-accounts/bulk',
198
190
  requiresUserAuth: true,
199
191
  readOnly: false,
200
192
  destructive: false,
@@ -205,7 +197,6 @@ const tools = [
205
197
  description: 'Update a chart-of-accounts entry by external UUID. Pass only the fields you want to change. Editable: `name`, `accountNumber`, `description`, `color`, `status` ("active" | "inactive"), `subTypeId` (must keep the same high-level `type` — switching a Bank account into an Income sub-type is rejected), `parentAccountId` (set to a valid parent id to nest, or pass `isChildAccount: false` to detach), `connectionSyncEnabled` (only meaningful on connected feed accounts). Do not send `type` (derived from `subTypeId`), `teamId`, `connectionId`, or `systemCreated` — they are stripped. System / non-editable accounts (e.g. payroll defaults) accept only a small allowlist of cosmetic fields plus name on physical accounts; expect a 403 if you try to edit a protected field. An empty body returns 400.',
206
198
  method: 'PATCH',
207
199
  pathTemplate: '/partners/chart-of-accounts/{id}',
208
- inputSchema: idBodyInputSchema,
209
200
  requiresUserAuth: true,
210
201
  readOnly: false,
211
202
  destructive: false,
@@ -213,10 +204,9 @@ const tools = [
213
204
  {
214
205
  name: 'COUNT_delete_account',
215
206
  title: 'Delete Account',
216
- description: 'Delete a chart of accounts entry by external UUID.',
207
+ description: 'Delete a chart of accounts entry by external UUID. Partner-created accounts are editable and deletable until they have journal entries, sub-accounts, invoice products, or payroll mappings. If delete fails because the account has journal entries, use `COUNT_update_account` with `status: inactive` instead. Call `COUNT_list_accounts` with `query.includeDeleteMeta: "true"` to read `canDelete` and `deleteBlockedReason` before attempting delete.',
217
208
  method: 'DELETE',
218
209
  pathTemplate: '/partners/chart-of-accounts/{id}',
219
- inputSchema: idInputSchema,
220
210
  requiresUserAuth: true,
221
211
  readOnly: false,
222
212
  destructive: true,
@@ -227,7 +217,6 @@ const tools = [
227
217
  description: 'List vendors for the authenticated COUNT workspace.',
228
218
  method: 'GET',
229
219
  pathTemplate: '/partners/vendors',
230
- inputSchema: queryInputSchema,
231
220
  requiresUserAuth: true,
232
221
  readOnly: true,
233
222
  destructive: false,
@@ -238,7 +227,6 @@ const tools = [
238
227
  description: 'Create a vendor.',
239
228
  method: 'POST',
240
229
  pathTemplate: '/partners/vendors',
241
- inputSchema: bodyInputSchema,
242
230
  requiresUserAuth: true,
243
231
  readOnly: false,
244
232
  destructive: false,
@@ -249,7 +237,6 @@ const tools = [
249
237
  description: 'Update a vendor by external UUID.',
250
238
  method: 'PATCH',
251
239
  pathTemplate: '/partners/vendors/{id}',
252
- inputSchema: idBodyInputSchema,
253
240
  requiresUserAuth: true,
254
241
  readOnly: false,
255
242
  destructive: false,
@@ -260,7 +247,6 @@ const tools = [
260
247
  description: 'Delete a vendor by external UUID.',
261
248
  method: 'DELETE',
262
249
  pathTemplate: '/partners/vendors/{id}',
263
- inputSchema: idInputSchema,
264
250
  requiresUserAuth: true,
265
251
  readOnly: false,
266
252
  destructive: true,
@@ -271,7 +257,6 @@ const tools = [
271
257
  description: 'List customers for the authenticated COUNT workspace.',
272
258
  method: 'GET',
273
259
  pathTemplate: '/partners/customers',
274
- inputSchema: queryInputSchema,
275
260
  requiresUserAuth: true,
276
261
  readOnly: true,
277
262
  destructive: false,
@@ -282,7 +267,6 @@ const tools = [
282
267
  description: 'Get a customer by external UUID.',
283
268
  method: 'GET',
284
269
  pathTemplate: '/partners/customers/{id}',
285
- inputSchema: idInputSchema,
286
270
  requiresUserAuth: true,
287
271
  readOnly: true,
288
272
  destructive: false,
@@ -293,7 +277,6 @@ const tools = [
293
277
  description: 'Create a customer. Required body: `customer` (business or person name). Optional body: `email`, `mainPhone`, `website`, `notes`, `status` (`active` | `inactive`), `paymentTerm`, `taxNumber`, `taxAutoCalculate`, `taxExcluded`, `taxes` (numeric tax ids), `billingAddress`, `shippingAddress`, `contacts: [{ firstName?, lastName?, email?, phone?, isPrimary? }]`. Returns 201 with the created customer; `id` in the response is the external UUID.',
294
278
  method: 'POST',
295
279
  pathTemplate: '/partners/customers',
296
- inputSchema: bodyInputSchema,
297
280
  requiresUserAuth: true,
298
281
  readOnly: false,
299
282
  destructive: false,
@@ -304,7 +287,6 @@ const tools = [
304
287
  description: 'Update a customer by external UUID.',
305
288
  method: 'PUT',
306
289
  pathTemplate: '/partners/customers/{id}',
307
- inputSchema: idBodyInputSchema,
308
290
  requiresUserAuth: true,
309
291
  readOnly: false,
310
292
  destructive: false,
@@ -315,7 +297,6 @@ const tools = [
315
297
  description: 'Create up to 100 customers in a single call. Body: `{ "customers": [<row>, ...] }` where each `<row>` uses the EXACT same shape as `COUNT_create_customer` (required `customer`; optional `email`, `mainPhone`, `website`, `notes`, `status`, `paymentTerm`, `taxNumber`, `taxAutoCalculate`, `taxExcluded`, `taxes`, `billingAddress`, `shippingAddress`, `contacts`). **Partial-success contract**: each row runs in its own DB transaction; one row failing never rolls back another row\'s writes. Response envelope: `{ "successCount": N, "errorCount": M, "results": [{ "index": 0, "success": true, "customer": {...} } | { "index": 1, "success": false, "error": "..." }, ...] }` — read `errorCount` first; if non-zero, iterate `results` and only retry the rows where `success: false`. The HTTP status is always `201` when the batch was accepted, even if all rows failed. **Cap**: 100 rows per call. **Recommended batch size**: ~25 rows for large migrations (e.g. importing customers from an external billing system).',
316
298
  method: 'POST',
317
299
  pathTemplate: '/partners/customers/bulk',
318
- inputSchema: bodyInputSchema,
319
300
  requiresUserAuth: true,
320
301
  readOnly: false,
321
302
  destructive: false,
@@ -326,7 +307,6 @@ const tools = [
326
307
  description: 'Update up to 100 customers in a single call. Body: `{ "customers": [{ "uuid": "<external COUNT customer id>", ...fields }] }` where each row must include `uuid` plus the fields to patch (same writable fields as `COUNT_update_customer`, e.g. `email`, `mainPhone`, `website`, `notes`, `status`, `paymentTerm`, `billingAddress`, `shippingAddress`, `contacts`). **Partial-success contract**: each row is updated independently; one row failing never rolls back another row\'s writes. Response envelope: `{ "successCount": N, "errorCount": M, "results": [{ "index": 0, "success": true, "customer": {...} } | { "index": 1, "success": false, "error": "..." }, ...] }`. The HTTP status is always `201` when the batch was accepted. **Recommended batch size**: ~25 rows for backfilling email/phone on migrated customers.',
327
308
  method: 'PATCH',
328
309
  pathTemplate: '/partners/customers/bulk',
329
- inputSchema: bodyInputSchema,
330
310
  requiresUserAuth: true,
331
311
  readOnly: false,
332
312
  destructive: false,
@@ -337,7 +317,6 @@ const tools = [
337
317
  description: 'Delete a customer by external UUID.',
338
318
  method: 'DELETE',
339
319
  pathTemplate: '/partners/customers/{id}',
340
- inputSchema: idInputSchema,
341
320
  requiresUserAuth: true,
342
321
  readOnly: false,
343
322
  destructive: true,
@@ -348,7 +327,6 @@ const tools = [
348
327
  description: 'List people records for the authenticated COUNT workspace.',
349
328
  method: 'GET',
350
329
  pathTemplate: '/partners/people',
351
- inputSchema: queryInputSchema,
352
330
  requiresUserAuth: true,
353
331
  readOnly: true,
354
332
  destructive: false,
@@ -359,7 +337,6 @@ const tools = [
359
337
  description: 'Get a people record by external UUID.',
360
338
  method: 'GET',
361
339
  pathTemplate: '/partners/people/{id}',
362
- inputSchema: idInputSchema,
363
340
  requiresUserAuth: true,
364
341
  readOnly: true,
365
342
  destructive: false,
@@ -370,7 +347,6 @@ const tools = [
370
347
  description: 'List products and services for the authenticated COUNT workspace.',
371
348
  method: 'GET',
372
349
  pathTemplate: '/partners/products',
373
- inputSchema: queryInputSchema,
374
350
  requiresUserAuth: true,
375
351
  readOnly: true,
376
352
  destructive: false,
@@ -381,7 +357,6 @@ const tools = [
381
357
  description: 'Get a product or service by external UUID.',
382
358
  method: 'GET',
383
359
  pathTemplate: '/partners/products/{id}',
384
- inputSchema: idInputSchema,
385
360
  requiresUserAuth: true,
386
361
  readOnly: true,
387
362
  destructive: false,
@@ -389,10 +364,9 @@ const tools = [
389
364
  {
390
365
  name: 'COUNT_create_product',
391
366
  title: 'Create Product',
392
- description: 'Create a product or service.',
367
+ description: 'Create a product or service. Use `categoryAccountUuid` and `purchaseCategoryAccountUuid` (from `list_accounts`) for income/purchase category accounts — do not pass UUID-shaped values in `categoryAccountId`/`purchaseCategoryAccountId`. Use `taxUuids` (from `list_taxes`) for taxes.',
393
368
  method: 'POST',
394
369
  pathTemplate: '/partners/products',
395
- inputSchema: bodyInputSchema,
396
370
  requiresUserAuth: true,
397
371
  readOnly: false,
398
372
  destructive: false,
@@ -400,10 +374,9 @@ const tools = [
400
374
  {
401
375
  name: 'COUNT_update_product',
402
376
  title: 'Update Product',
403
- description: 'Update a product or service by external UUID.',
377
+ description: 'Update a product or service by external UUID. Use `categoryAccountUuid`, `purchaseCategoryAccountUuid`, and `taxUuids` for account/tax references (see `COUNT_create_product`).',
404
378
  method: 'PATCH',
405
379
  pathTemplate: '/partners/products/{id}',
406
- inputSchema: idBodyInputSchema,
407
380
  requiresUserAuth: true,
408
381
  readOnly: false,
409
382
  destructive: false,
@@ -414,7 +387,6 @@ const tools = [
414
387
  description: 'Delete a product or service by external UUID.',
415
388
  method: 'DELETE',
416
389
  pathTemplate: '/partners/products/{id}',
417
- inputSchema: idInputSchema,
418
390
  requiresUserAuth: true,
419
391
  readOnly: false,
420
392
  destructive: true,
@@ -425,7 +397,6 @@ const tools = [
425
397
  description: 'List tags for the authenticated COUNT workspace.',
426
398
  method: 'GET',
427
399
  pathTemplate: '/partners/tags',
428
- inputSchema: queryInputSchema,
429
400
  requiresUserAuth: true,
430
401
  readOnly: true,
431
402
  destructive: false,
@@ -436,7 +407,6 @@ const tools = [
436
407
  description: 'Get a tag by external UUID.',
437
408
  method: 'GET',
438
409
  pathTemplate: '/partners/tags/{id}',
439
- inputSchema: idInputSchema,
440
410
  requiresUserAuth: true,
441
411
  readOnly: true,
442
412
  destructive: false,
@@ -447,7 +417,6 @@ const tools = [
447
417
  description: 'Create a tag.',
448
418
  method: 'POST',
449
419
  pathTemplate: '/partners/tags',
450
- inputSchema: bodyInputSchema,
451
420
  requiresUserAuth: true,
452
421
  readOnly: false,
453
422
  destructive: false,
@@ -458,7 +427,6 @@ const tools = [
458
427
  description: 'Update a tag by external UUID.',
459
428
  method: 'PATCH',
460
429
  pathTemplate: '/partners/tags/{id}',
461
- inputSchema: idBodyInputSchema,
462
430
  requiresUserAuth: true,
463
431
  readOnly: false,
464
432
  destructive: false,
@@ -469,7 +437,6 @@ const tools = [
469
437
  description: 'Delete a tag by external UUID.',
470
438
  method: 'DELETE',
471
439
  pathTemplate: '/partners/tags/{id}',
472
- inputSchema: idInputSchema,
473
440
  requiresUserAuth: true,
474
441
  readOnly: false,
475
442
  destructive: true,
@@ -480,7 +447,6 @@ const tools = [
480
447
  description: 'List tag groups for the authenticated COUNT workspace.',
481
448
  method: 'GET',
482
449
  pathTemplate: '/partners/tags/groups',
483
- inputSchema: queryInputSchema,
484
450
  requiresUserAuth: true,
485
451
  readOnly: true,
486
452
  destructive: false,
@@ -491,7 +457,6 @@ const tools = [
491
457
  description: 'Get a tag group by external UUID.',
492
458
  method: 'GET',
493
459
  pathTemplate: '/partners/tags/groups/{id}',
494
- inputSchema: idInputSchema,
495
460
  requiresUserAuth: true,
496
461
  readOnly: true,
497
462
  destructive: false,
@@ -502,7 +467,6 @@ const tools = [
502
467
  description: 'Create a tag group. Body: `name` (required, string, unique per workspace), `color` (optional string), and `tagUuids` (optional array of tag UUIDs from `list_tags`) — each tag may belong to at most one group, so passing a tag already in another group rejects the request with a 400. Send tag UUIDs (not numeric ids); the server resolves them to internal ids automatically.',
503
468
  method: 'POST',
504
469
  pathTemplate: '/partners/tags/groups',
505
- inputSchema: bodyInputSchema,
506
470
  requiresUserAuth: true,
507
471
  readOnly: false,
508
472
  destructive: false,
@@ -513,7 +477,6 @@ const tools = [
513
477
  description: 'Update a tag group by external UUID. Body: optional `name`, `color`, and `tagUuids` (array of tag UUIDs from `list_tags`). When `tagUuids` is sent the group\'s membership is REPLACED with that exact set (existing tags not in the array are removed, new ones added); pass `[]` to clear all tags. A tag can only belong to one group at a time. Send tag UUIDs (not numeric ids); the server resolves them to internal ids automatically.',
514
478
  method: 'PATCH',
515
479
  pathTemplate: '/partners/tags/groups/{id}',
516
- inputSchema: idBodyInputSchema,
517
480
  requiresUserAuth: true,
518
481
  readOnly: false,
519
482
  destructive: false,
@@ -524,7 +487,6 @@ const tools = [
524
487
  description: 'Delete a tag group by external UUID.',
525
488
  method: 'DELETE',
526
489
  pathTemplate: '/partners/tags/groups/{id}',
527
- inputSchema: idInputSchema,
528
490
  requiresUserAuth: true,
529
491
  readOnly: false,
530
492
  destructive: true,
@@ -535,7 +497,6 @@ const tools = [
535
497
  description: 'List invoices for the authenticated COUNT workspace.',
536
498
  method: 'GET',
537
499
  pathTemplate: '/partners/invoices',
538
- inputSchema: queryInputSchema,
539
500
  requiresUserAuth: true,
540
501
  readOnly: true,
541
502
  destructive: false,
@@ -546,7 +507,6 @@ const tools = [
546
507
  description: 'Get an invoice by external UUID.',
547
508
  method: 'GET',
548
509
  pathTemplate: '/partners/invoices/{id}',
549
- inputSchema: idInputSchema,
550
510
  requiresUserAuth: true,
551
511
  readOnly: true,
552
512
  destructive: false,
@@ -557,7 +517,6 @@ const tools = [
557
517
  description: 'Create an invoice, credit memo, or estimate. Required body: `customerUuid` (from `list_customers`), `date` (YYYY-MM-DD), and `products: [{ productUuid | uuid, quantity, unitPrice, ... }]` (each line resolves a product UUID into the internal product id; `unitPrice` is per-unit in workspace currency). Optional body: `invoiceType` ("invoice" — default | "estimate" | "memo"), `invoiceNumber`, `dueDate` (YYYY-MM-DD), `currency` (defaults to workspace currency), `discount` + `discountDescription`, `notes`, `tagUuids: string[]` (replaces all tags), `appliedToInvoiceUuid` (only for `invoiceType: "memo"` — links the credit memo to the original invoice), `isDraft` (boolean — drafts skip approval / journals), `applyCredit` + `creditMemo: { id, amount }` (apply an existing memo), and recurring-template fields (`recurrencePattern`, `inAdvanceCreationDays`) when creating a recurring schedule via this endpoint. **Do not send numeric ids for `customer`/`products[].id`/`tags`/`appliedToInvoiceId`** — the partner middleware resolves UUIDs into them. Memos cannot be recurring; recurring memos return 400.',
558
518
  method: 'POST',
559
519
  pathTemplate: '/partners/invoices',
560
- inputSchema: bodyInputSchema,
561
520
  requiresUserAuth: true,
562
521
  readOnly: false,
563
522
  destructive: false,
@@ -570,7 +529,6 @@ const tools = [
570
529
  'When the body is empty or contains internal-only or unknown fields, the response is still 200 with the unchanged invoice but includes a `_partnerWarnings: [{ field, reason, message }]` array describing each ignored field — read it before assuming the update succeeded.',
571
530
  method: 'PATCH',
572
531
  pathTemplate: '/partners/invoices/{id}',
573
- inputSchema: idBodyInputSchema,
574
532
  requiresUserAuth: true,
575
533
  readOnly: false,
576
534
  destructive: false,
@@ -581,7 +539,6 @@ const tools = [
581
539
  description: 'Delete an invoice by external UUID.',
582
540
  method: 'DELETE',
583
541
  pathTemplate: '/partners/invoices/{id}',
584
- inputSchema: idInputSchema,
585
542
  requiresUserAuth: true,
586
543
  readOnly: false,
587
544
  destructive: true,
@@ -592,7 +549,6 @@ const tools = [
592
549
  description: 'Approve a draft invoice by external UUID. Moves the invoice from `draft` → `approved`, posts the underlying journal entries, and makes the invoice eligible for `send`. Body is optional — pass nothing for the standard approval. Errors with 400 if the invoice is already approved, sent, or paid; not valid for `invoiceType: "memo"` drafts (use the regular update + send flow on memos).',
593
550
  method: 'PATCH',
594
551
  pathTemplate: '/partners/invoices/{id}/approve',
595
- inputSchema: idOptionalBodyInputSchema,
596
552
  requiresUserAuth: true,
597
553
  readOnly: false,
598
554
  destructive: false,
@@ -603,7 +559,6 @@ const tools = [
603
559
  description: 'Send (or re-send) an approved invoice to its customer by external UUID. Optional body: `recipients: string[]` (override the customer\'s default email), `subject`, `message`, `attachPdf: boolean` (defaults to true), `cc: string[]`, `bcc: string[]`. The invoice must be approved first via `approve_invoice`; calling `send` on a draft returns 400. After a successful send the invoice transitions to `sent`/`unpaid` and becomes the canonical version the customer sees in the public link.',
604
560
  method: 'POST',
605
561
  pathTemplate: '/partners/invoices/{id}/send',
606
- inputSchema: idOptionalBodyInputSchema,
607
562
  requiresUserAuth: true,
608
563
  readOnly: false,
609
564
  destructive: false,
@@ -614,7 +569,6 @@ const tools = [
614
569
  description: 'Get a public payment/view link for an invoice by external UUID.',
615
570
  method: 'GET',
616
571
  pathTemplate: '/partners/invoices/{id}/public-link',
617
- inputSchema: idInputSchema,
618
572
  requiresUserAuth: true,
619
573
  readOnly: true,
620
574
  destructive: false,
@@ -625,7 +579,6 @@ const tools = [
625
579
  description: 'Get the audit log for an invoice by external UUID.',
626
580
  method: 'GET',
627
581
  pathTemplate: '/partners/invoices/{id}/audit-log',
628
- inputSchema: idInputSchema,
629
582
  requiresUserAuth: true,
630
583
  readOnly: true,
631
584
  destructive: false,
@@ -636,7 +589,6 @@ const tools = [
636
589
  description: 'Attach documents to an invoice (or estimate / memo) by external UUID using already-hosted URLs — the COUNT server fetches each URL once and stores it as a regular invoice attachment. Body: `{ attachments: [{ url: string, title?: string }] }`. The URL must be publicly fetchable from the COUNT server (no signed-cookie / VPN-only links). For uploading raw bytes use the dedicated `/attachments/upload` multipart endpoint instead — that path is not exposed as an MCP tool because MCP tools cannot send multipart bodies.',
637
590
  method: 'POST',
638
591
  pathTemplate: '/partners/invoices/{id}/attachments',
639
- inputSchema: idBodyInputSchema,
640
592
  requiresUserAuth: true,
641
593
  readOnly: false,
642
594
  destructive: false,
@@ -647,7 +599,6 @@ const tools = [
647
599
  description: 'Apply one or more credit memos to a single invoice by external UUID. Body: `{ creditMemos: [{ id: <invoice or memo UUID>, amount: <decimal> }, ...] }`. Each `id` accepts a UUID, a numeric id, or a numeric string — the partner middleware resolves them and asserts each memo belongs to the same workspace. The sum of `amount` values cannot exceed the open balance of the target invoice or the available balance on the memos; the request rolls back atomically if any memo is over-applied.',
648
600
  method: 'PATCH',
649
601
  pathTemplate: '/partners/invoices/{id}/apply-multiple-credit-to-invoice',
650
- inputSchema: idBodyInputSchema,
651
602
  requiresUserAuth: true,
652
603
  readOnly: false,
653
604
  destructive: false,
@@ -658,7 +609,6 @@ const tools = [
658
609
  description: 'Apply a single credit memo (the URL `id`) across multiple target invoices in one call. Body: `{ invoices: [{ id: <invoice UUID or numeric id>, amount: <decimal> }, ...] }`. The URL `id` is the credit memo to draw from; each entry in `invoices` is a target invoice plus the dollar amount of the memo to apply to it. Total applied amount cannot exceed the memo\'s remaining balance. The partner middleware resolves UUIDs to numeric ids and rejects cross-workspace references with 404.',
659
610
  method: 'PATCH',
660
611
  pathTemplate: '/partners/invoices/{id}/apply-credit-to-multiple-invoices',
661
- inputSchema: idBodyInputSchema,
662
612
  requiresUserAuth: true,
663
613
  readOnly: false,
664
614
  destructive: false,
@@ -669,7 +619,6 @@ const tools = [
669
619
  description: 'Remove a previously-applied credit memo from an invoice by external UUID. Body: `{ memoId: <credit memo UUID or numeric id> }`. The memo\'s balance is restored and the target invoice\'s open balance increases by the unapplied amount. 404 if the memo isn\'t currently applied to this invoice.',
670
620
  method: 'PATCH',
671
621
  pathTemplate: '/partners/invoices/{id}/remove-credit',
672
- inputSchema: idBodyInputSchema,
673
622
  requiresUserAuth: true,
674
623
  readOnly: false,
675
624
  destructive: false,
@@ -680,7 +629,6 @@ const tools = [
680
629
  description: 'Remove a previously-applied transaction payment from an invoice by external UUID. Body: `{ transactionId: <transaction UUID from list_transactions>, withCaution?: boolean }`. Restores the invoice open balance and unlinks the transaction from Accounts Receivable. Manual payment links only — automated Stripe links may not be removable via this endpoint.',
681
630
  method: 'PATCH',
682
631
  pathTemplate: '/partners/invoices/{id}/remove-transaction',
683
- inputSchema: idBodyInputSchema,
684
632
  requiresUserAuth: true,
685
633
  readOnly: false,
686
634
  destructive: false,
@@ -691,7 +639,6 @@ const tools = [
691
639
  description: 'List recurring invoice templates for the authenticated COUNT workspace.',
692
640
  method: 'GET',
693
641
  pathTemplate: '/partners/recurring-invoice-templates',
694
- inputSchema: queryInputSchema,
695
642
  requiresUserAuth: true,
696
643
  readOnly: true,
697
644
  destructive: false,
@@ -702,7 +649,6 @@ const tools = [
702
649
  description: 'Get a recurring invoice template by external UUID.',
703
650
  method: 'GET',
704
651
  pathTemplate: '/partners/recurring-invoice-templates/{id}',
705
- inputSchema: idInputSchema,
706
652
  requiresUserAuth: true,
707
653
  readOnly: true,
708
654
  destructive: false,
@@ -710,10 +656,9 @@ const tools = [
710
656
  {
711
657
  name: 'COUNT_create_recurring_invoice_template',
712
658
  title: 'Create Recurring Invoice Template',
713
- description: 'Create a recurring invoice template. Same body shape as `create_invoice` (`customerUuid`, `products`, etc.) PLUS a required `recurrencePattern` object describing the cadence (e.g. monthly, weekly). Optional `inAdvanceCreationDays` controls how many days before each scheduled date the next invoice instance is generated. `invoiceType` defaults to "invoice"; "memo" is rejected (credit memos cannot recur). `isDraft` defaults to true so the template is paused until you explicitly resume it via `resume_recurring_invoice_template`.',
659
+ description: 'Create a recurring invoice template. Same body shape as `create_invoice` (`customerUuid`, `products`, etc.) PLUS a required `recurrencePattern` string cadence (daily, weekly, biweekly, monthly, quarterly, or yearly). Optional `inAdvanceCreationDays` controls how many days before each scheduled date the next invoice instance is generated. `invoiceType` defaults to "invoice"; "memo" is rejected (credit memos cannot recur). `isDraft` defaults to true so the template is paused until you explicitly resume it via `resume_recurring_invoice_template`.',
714
660
  method: 'POST',
715
661
  pathTemplate: '/partners/recurring-invoice-templates',
716
- inputSchema: bodyInputSchema,
717
662
  requiresUserAuth: true,
718
663
  readOnly: false,
719
664
  destructive: false,
@@ -724,7 +669,6 @@ const tools = [
724
669
  description: 'Update a recurring invoice template by external UUID.',
725
670
  method: 'PATCH',
726
671
  pathTemplate: '/partners/recurring-invoice-templates/{id}',
727
- inputSchema: idBodyInputSchema,
728
672
  requiresUserAuth: true,
729
673
  readOnly: false,
730
674
  destructive: false,
@@ -735,7 +679,6 @@ const tools = [
735
679
  description: 'Delete a recurring invoice template by external UUID.',
736
680
  method: 'DELETE',
737
681
  pathTemplate: '/partners/recurring-invoice-templates/{id}',
738
- inputSchema: idInputSchema,
739
682
  requiresUserAuth: true,
740
683
  readOnly: false,
741
684
  destructive: true,
@@ -746,7 +689,6 @@ const tools = [
746
689
  description: 'Pause a recurring invoice template by external UUID.',
747
690
  method: 'POST',
748
691
  pathTemplate: '/partners/recurring-invoice-templates/{id}/pause',
749
- inputSchema: idOptionalBodyInputSchema,
750
692
  requiresUserAuth: true,
751
693
  readOnly: false,
752
694
  destructive: false,
@@ -757,7 +699,6 @@ const tools = [
757
699
  description: 'Resume a recurring invoice template by external UUID.',
758
700
  method: 'POST',
759
701
  pathTemplate: '/partners/recurring-invoice-templates/{id}/resume',
760
- inputSchema: idOptionalBodyInputSchema,
761
702
  requiresUserAuth: true,
762
703
  readOnly: false,
763
704
  destructive: false,
@@ -765,10 +706,9 @@ const tools = [
765
706
  {
766
707
  name: 'COUNT_list_bills',
767
708
  title: 'List Bills',
768
- description: 'List vendor bills for the authenticated workspace. Common `query` filters: `page`, `limit` (default 50), `orderBy`, `orderDirection`, `projectUuids` (comma-separated project UUIDs), `vendorUuids` (comma-separated vendor UUIDs from `list_vendors` — do NOT pass numeric `vendors`), `approvalStatus`, `status` (comma-separated combination of approval + payment status tokens, e.g. `"draft,approved,paid"`), `billType` (`"bill"` | `"vendor_memo"`), `currency`, `search` (substring against bill number / vendor name), `startDate` / `endDate` (bill-date window, YYYY-MM-DD), `startDueDate` / `endDueDate` (due-date window), `amount` + `amountOperator` (e.g. `amount=100&amountOperator=gte`). Returns bill rows with `id` exposed as the external UUID pass that UUID back to `get_bill` / `update_bill` / `approve_bill` / `delete_bill`.',
709
+ description: 'List vendor bills for the authenticated workspace. Returns bill rows with `id` as the external UUID. Filter fields are documented on the `query` input schema.',
769
710
  method: 'GET',
770
711
  pathTemplate: '/partners/bills',
771
- inputSchema: queryInputSchema,
772
712
  requiresUserAuth: true,
773
713
  readOnly: true,
774
714
  destructive: false,
@@ -779,7 +719,6 @@ const tools = [
779
719
  description: 'Get a single bill by external UUID. Returns the bill record with line items, attachments, payments, current approval/payment status, and the linked vendor. Note: this endpoint also returns soft-deleted bills, so check the `isDeleted` field on the response if you need to filter them out client-side.',
780
720
  method: 'GET',
781
721
  pathTemplate: '/partners/bills/{id}',
782
- inputSchema: idInputSchema,
783
722
  requiresUserAuth: true,
784
723
  readOnly: true,
785
724
  destructive: false,
@@ -790,7 +729,6 @@ const tools = [
790
729
  description: 'Create a vendor bill (accounts payable). Required body: `vendorUuid` (vendor UUID from `list_vendors`), `date` (YYYY-MM-DD), `dueDate` (YYYY-MM-DD), and `lineItems: [{ categoryAccountUuid, description?, quantity, price, total?, taxes?, projectUuid?, customerUuid? }]` where `categoryAccountUuid` is the expense/category account UUID from `list_accounts`. Optional body: `billNumber`, `purchaseOrderNumber`, `notes`, `currency` (defaults to workspace currency), `attachments: [{ url, title? }]`, `tagUuids: string[]`, `projectUuid`. Do NOT send numeric `vendorId`, `categoryAccountId`, or `tags` — use UUID fields only. The created bill enters the `draft` state; call `approve_bill` to post journals and make it eligible for payment. Returns 201 with the bill, or a `{ statusCode, message }` error envelope on validation failure.',
791
730
  method: 'POST',
792
731
  pathTemplate: '/partners/bills',
793
- inputSchema: bodyInputSchema,
794
732
  requiresUserAuth: true,
795
733
  readOnly: false,
796
734
  destructive: false,
@@ -801,7 +739,6 @@ const tools = [
801
739
  description: 'Update a bill by external UUID. Pass only the fields you want to change. Editable: `vendorUuid`, `date`, `dueDate`, `billNumber`, `purchaseOrderNumber`, `notes`, `currency`, `lineItems` (replaces all lines when provided; each line uses `categoryAccountUuid`, `quantity`, `price`, optional `projectUuid`, `customerUuid`), `tagUuids`, `projectUuid`, `attachments`. Do NOT send numeric `vendorId`, `categoryAccountId`, or `tags`. Approved/paid bills have a more restrictive editable set — expect 400 on date/line changes after approval; use `delete_bill` + recreate if you need a structural change post-approval.',
802
740
  method: 'PATCH',
803
741
  pathTemplate: '/partners/bills/{id}',
804
- inputSchema: idBodyInputSchema,
805
742
  requiresUserAuth: true,
806
743
  readOnly: false,
807
744
  destructive: false,
@@ -812,7 +749,6 @@ const tools = [
812
749
  description: 'Delete a bill by external UUID. Soft-delete on draft bills; bills that have any payments applied (`paidAmount > 0`) cannot be deleted and return 400 — unapply payments first via the standard payment endpoints.',
813
750
  method: 'DELETE',
814
751
  pathTemplate: '/partners/bills/{id}',
815
- inputSchema: idInputSchema,
816
752
  requiresUserAuth: true,
817
753
  readOnly: false,
818
754
  destructive: true,
@@ -823,7 +759,6 @@ const tools = [
823
759
  description: 'Approve a draft bill by external UUID. Posts the underlying journal entries (debit each line\'s expense account, credit Accounts Payable) and makes the bill eligible for payment. Body is optional. Errors with 400 if the bill is already approved or has no line items.',
824
760
  method: 'POST',
825
761
  pathTemplate: '/partners/bills/{id}/approve',
826
- inputSchema: idOptionalBodyInputSchema,
827
762
  requiresUserAuth: true,
828
763
  readOnly: false,
829
764
  destructive: false,
@@ -834,7 +769,6 @@ const tools = [
834
769
  description: 'Apply one or more vendor credit memos toward an approved bill by external UUID. Body: `{ memosBills: [{ id: <vendor-memo bill UUID from list_bills with billType vendor_memo>, amount: <decimal> }, ...] }`. Each memo must belong to the same vendor as the target bill; total applied cannot exceed the bill open balance or memo available balance. Vendor memos are bill records with `billType: "vendor_memo"` — use their UUID as `id`.',
835
770
  method: 'POST',
836
771
  pathTemplate: '/partners/bills/{id}/apply-vendor-memos',
837
- inputSchema: idBodyInputSchema,
838
772
  requiresUserAuth: true,
839
773
  readOnly: false,
840
774
  destructive: false,
@@ -845,7 +779,6 @@ const tools = [
845
779
  description: 'Remove a previously-applied transaction payment from a bill by external UUID. Body: `{ transactionId: <transaction UUID from list_transactions>, withCaution?: boolean }`. Restores the bill open balance and clears the transaction\'s bill link.',
846
780
  method: 'POST',
847
781
  pathTemplate: '/partners/bills/{id}/unassign-transaction',
848
- inputSchema: idBodyInputSchema,
849
782
  requiresUserAuth: true,
850
783
  readOnly: false,
851
784
  destructive: false,
@@ -856,7 +789,6 @@ const tools = [
856
789
  description: 'List journal entries for the authenticated COUNT workspace.',
857
790
  method: 'GET',
858
791
  pathTemplate: '/partners/journal-entries',
859
- inputSchema: queryInputSchema,
860
792
  requiresUserAuth: true,
861
793
  readOnly: true,
862
794
  destructive: false,
@@ -867,7 +799,6 @@ const tools = [
867
799
  description: 'Create a manual journal entry. Body: required `descriptionEntry` (memo), `date` (YYYY-MM-DD), and `lines` (array). Each line needs `accountUuid` (the `id`, `accUuid`, or `accountUuid` from `list_accounts`) plus exactly one of `amountDebit` or `amountCredit` (decimal numbers as strings or numbers — never both on the same line, never neither). Optional per-line: `descriptionLine`. Optional top-level: `refNumber`, `withCaution: true` to skip book-closeness checks (use sparingly). The server validates each account exists in this workspace before posting; manual entries can fail if any line\'s posting date falls within a reconciled period for that account.',
868
800
  method: 'POST',
869
801
  pathTemplate: '/partners/journal-entries',
870
- inputSchema: bodyInputSchema,
871
802
  requiresUserAuth: true,
872
803
  readOnly: false,
873
804
  destructive: false,
@@ -878,7 +809,6 @@ const tools = [
878
809
  description: 'Create up to 100 manual journal entry postings in a single call. Body: `{ "journalEntries": [<posting>, ...] }` where each `<posting>` uses the EXACT same shape as `COUNT_create_journal_entry` (required `descriptionEntry`, `date`, `lines`; each line needs `accountUuid` + exactly one of `amountDebit` / `amountCredit`; optional per-line `descriptionLine`; optional top-level `refNumber`, `withCaution`). **Partial-success contract**: each posting runs in its own DB transaction; one bad posting never rolls back another posting\'s writes. Response envelope: `{ "successCount": N, "errorCount": M, "results": [{ "index": 0, "success": true, "journalEntry": [...lines] } | { "index": 1, "success": false, "error": "..." }, ...] }` — read `errorCount` first; if non-zero, iterate `results` and only retry the postings where `success: false`. The HTTP status is always `201` when the batch was accepted, even if all postings failed. **Cap**: 100 postings per call (split larger imports into multiple calls). **Recommended batch size**: ~25 postings for large historical imports; larger batches increase timeout risk. **Performance note**: account UUIDs are resolved in a SINGLE batched lookup across the whole batch, so a 100-posting × 10-line request issues one account query — bulk is strictly cheaper than calling `COUNT_create_journal_entry` 100 times. **Use cases**: replay manual postings from another GL, backfill accruals, ingest a CSV of journal entries. For a single posting, prefer `COUNT_create_journal_entry` — the response shape is simpler.',
879
810
  method: 'POST',
880
811
  pathTemplate: '/partners/journal-entries/bulk',
881
- inputSchema: bodyInputSchema,
882
812
  requiresUserAuth: true,
883
813
  readOnly: false,
884
814
  destructive: false,
@@ -886,10 +816,9 @@ const tools = [
886
816
  {
887
817
  name: 'COUNT_update_journal_entry',
888
818
  title: 'Update Journal Entry',
889
- description: 'Update a manually-created journal entry by external UUID. Same body shape as `create_journal_entry` (top-level `descriptionEntry`, `date`, `refNumber`, plus the full replacement `lines` array each line takes `accountUuid` and exactly one of `amountDebit`/`amountCredit`). Only manually-created entries (and Square integration entries) can be updated; system-generated journal entries from invoices, bills, payroll, etc. are read-only and return 400.',
819
+ description: 'Update a manually-created journal entry by external UUID. PATCH accepts partial bodies send only the fields you want to change (`descriptionEntry`, `date`, `refNumber`, and/or the full replacement `lines` array). When `lines` is included, each line takes `accountUuid` and exactly one of `amountDebit`/`amountCredit`. Only manually-created entries (and Square integration entries) can be updated; system-generated journal entries from invoices, bills, payroll, etc. are read-only and return 400.',
890
820
  method: 'PATCH',
891
821
  pathTemplate: '/partners/journal-entries/{id}',
892
- inputSchema: idBodyInputSchema,
893
822
  requiresUserAuth: true,
894
823
  readOnly: false,
895
824
  destructive: false,
@@ -900,7 +829,6 @@ const tools = [
900
829
  description: 'Delete a journal entry by external UUID.',
901
830
  method: 'DELETE',
902
831
  pathTemplate: '/partners/journal-entries/{id}',
903
- inputSchema: idInputSchema,
904
832
  requiresUserAuth: true,
905
833
  readOnly: false,
906
834
  destructive: true,
@@ -911,7 +839,6 @@ const tools = [
911
839
  description: 'List tasks for the authenticated workspace. `query` filters that accept comma-separated tokens where each token is EITHER an external UUID OR a workspace-scoped numeric id (partner middleware resolves both): `statusId` (task statuses), `assigneeId` / `assignerId` / `createdById` (workspace user references), `projects` (project filter), `commentId` (single token only — passing multiple returns 400). Pass-through filters: `page`, `limit`, `orderBy`, `orderDirection`, `search` (ILIKE on task name), `customId`, `visibility` (`"firm-team"` | `"team-only"` | `"firm-only"` — defaults to `["firm-team","team-only"]` when omitted), `priority`, `overduesOnly`, `deadlineDate` / `createdAt` / `updatedAt` (date windows), `status` (string `task.status` column). The legacy filters `clients`, `openTaskOnly`, and `isForClosing` are always stripped server-side. Numeric internal foreign keys are removed from response rows.',
912
840
  method: 'GET',
913
841
  pathTemplate: '/partners/tasks',
914
- inputSchema: queryInputSchema,
915
842
  requiresUserAuth: true,
916
843
  readOnly: true,
917
844
  destructive: false,
@@ -922,7 +849,6 @@ const tools = [
922
849
  description: 'Get a single task by external UUID. Default visibility is `["firm-team","team-only"]`; tasks created with `firm-only` visibility (notably system-generated INTERNAL_TASK rows) return 404 unless you explicitly pass `visibility=firm-only` under `query`.',
923
850
  method: 'GET',
924
851
  pathTemplate: '/partners/tasks/{id}',
925
- inputSchema: idInputSchema,
926
852
  requiresUserAuth: true,
927
853
  readOnly: true,
928
854
  destructive: false,
@@ -933,7 +859,6 @@ const tools = [
933
859
  description: 'Create a task. Required body: `type` (string — the task type identifier). The following optional FK fields each accept EITHER an external UUID OR a workspace-scoped numeric id (partner middleware resolves both and verifies workspace scope): `assigneeId`, `statusId`, `onCompleteStatusId`, `onCompleteAssigneeId`, `projectId`, `billId`, `transactionId`, `taskListId`, `taskListTemplateId`. Other safe body fields: `name`, `description`, `priority`, `deadline` (YYYY-MM-DD), `customId`, `visibility`. `tags` accepts an array OR a JSON-encoded string — every element must be the same kind (all UUIDs or all positive integer ids); mixed inputs return 400. If `assigneeId` is omitted it defaults to the partner-acting user. INTERNAL_TASK rows are always created with `firm-only` visibility regardless of the `visibility` value you pass. The partner endpoint is JSON-only — multipart attachments are not supported.',
934
860
  method: 'POST',
935
861
  pathTemplate: '/partners/tasks',
936
- inputSchema: bodyInputSchema,
937
862
  requiresUserAuth: true,
938
863
  readOnly: false,
939
864
  destructive: false,
@@ -944,7 +869,6 @@ const tools = [
944
869
  description: 'Update a task by external UUID. Pass only the fields you want to change; the same body shape as `create_task` applies. `tags` accepts either an array or a JSON-encoded string and replaces all tags atomically. UUID-resolvable optional FKs: `assigneeId`, `statusId`, `onCompleteStatusId`, `onCompleteAssigneeId`, `projectId`, `billId`, `transactionId`, `taskListId`, `taskListTemplateId` — each accepts a UUID or a workspace-scoped numeric id.',
945
870
  method: 'PATCH',
946
871
  pathTemplate: '/partners/tasks/{id}',
947
- inputSchema: idBodyInputSchema,
948
872
  requiresUserAuth: true,
949
873
  readOnly: false,
950
874
  destructive: false,
@@ -955,7 +879,6 @@ const tools = [
955
879
  description: 'Delete a task by external UUID.',
956
880
  method: 'DELETE',
957
881
  pathTemplate: '/partners/tasks/{id}',
958
- inputSchema: idInputSchema,
959
882
  requiresUserAuth: true,
960
883
  readOnly: false,
961
884
  destructive: true,
@@ -966,7 +889,6 @@ const tools = [
966
889
  description: 'List projects for the authenticated workspace. Soft-deleted projects are always excluded. `query` filters: `page` (default 1), `limit` (default 20), `search` (ILIKE on project name), `customerUuids` (comma-separated customer UUIDs — partner middleware resolves these into the internal `customers` filter), and `statusUuids` (comma-separated project-status UUIDs — discover them via `list_project_statuses`). The internal `customers`, `statusIds`, and `status` filters are stripped server-side; only the `*Uuids` variants take effect.',
967
890
  method: 'GET',
968
891
  pathTemplate: '/partners/projects',
969
- inputSchema: queryInputSchema,
970
892
  requiresUserAuth: true,
971
893
  readOnly: true,
972
894
  destructive: false,
@@ -977,7 +899,6 @@ const tools = [
977
899
  description: 'List the project statuses available in the authenticated workspace. Returns each status with its name, color, ordering, and external UUID — use the returned UUID as `statusUuid` on `create_project` / `update_project`, or in `statusUuids` (CSV) on `list_projects`.',
978
900
  method: 'GET',
979
901
  pathTemplate: '/partners/projects/statuses',
980
- inputSchema: queryInputSchema,
981
902
  requiresUserAuth: true,
982
903
  readOnly: true,
983
904
  destructive: false,
@@ -988,7 +909,6 @@ const tools = [
988
909
  description: 'Get a single project by external UUID. Soft-deleted projects return 404.',
989
910
  method: 'GET',
990
911
  pathTemplate: '/partners/projects/{id}',
991
- inputSchema: idInputSchema,
992
912
  requiresUserAuth: true,
993
913
  readOnly: true,
994
914
  destructive: false,
@@ -999,7 +919,6 @@ const tools = [
999
919
  description: 'List the tasks attached to a project by external UUID. `query` filters: `page` (default 1), `limit` (default 20), `search` (ILIKE on task name). Returns the same partner-sanitized task envelope as `list_tasks`.',
1000
920
  method: 'GET',
1001
921
  pathTemplate: '/partners/projects/{id}/tasks',
1002
- inputSchema: idInputSchema,
1003
922
  requiresUserAuth: true,
1004
923
  readOnly: true,
1005
924
  destructive: false,
@@ -1010,7 +929,6 @@ const tools = [
1010
929
  description: 'Create a project. Required body: non-empty `name`. Optional body: `customerUuid` (from `list_customers`; pass `null` or omit to leave the project uncustomered), `statusUuid` (from `list_project_statuses` — defaults to the workspace\'s default status if omitted), plus any safe project columns (e.g. `description`, `customId`, `startDate`, `endDate`). The numeric internal FKs `customerId`, `statusId`, and `teamId` are always stripped before the request is processed — use the `*Uuid` variants instead.',
1011
930
  method: 'POST',
1012
931
  pathTemplate: '/partners/projects',
1013
- inputSchema: bodyInputSchema,
1014
932
  requiresUserAuth: true,
1015
933
  readOnly: false,
1016
934
  destructive: false,
@@ -1021,7 +939,6 @@ const tools = [
1021
939
  description: 'Update a project by external UUID. Pass only the fields you want to change. `customerUuid` accepts a UUID (set the customer), `null`/`""` (clear the customer), or omission (leave unchanged). `statusUuid` accepts a UUID (set the status) or omission (leave unchanged). The numeric `customerId`, `statusId`, and `teamId` fields are stripped server-side. If you pass `customId` matching the existing value it may be discarded silently.',
1022
940
  method: 'PATCH',
1023
941
  pathTemplate: '/partners/projects/{id}',
1024
- inputSchema: idBodyInputSchema,
1025
942
  requiresUserAuth: true,
1026
943
  readOnly: false,
1027
944
  destructive: false,
@@ -1032,7 +949,6 @@ const tools = [
1032
949
  description: 'Delete a project by external UUID (soft delete).',
1033
950
  method: 'DELETE',
1034
951
  pathTemplate: '/partners/projects/{id}',
1035
- inputSchema: idInputSchema,
1036
952
  requiresUserAuth: true,
1037
953
  readOnly: false,
1038
954
  destructive: true,
@@ -1043,7 +959,6 @@ const tools = [
1043
959
  description: 'List time entries for the authenticated workspace. Soft-deleted entries are always excluded. `query` filters: `page` (default 1), `limit` (default 50), and the comma-separated UUID filters `peopleUuids`, `projectUuids`, `customerUuids`, `productServiceUuids` (each resolved into its internal numeric filter by the partner middleware). Direct numeric filters (`people`, `projects`, `customers`, `productServices`) are stripped server-side; only the `*Uuids` variants take effect. Date window filter: both `startDate` AND `endDate` (YYYY-MM-DD) must be provided together for the `createdAt`-range filter to apply — passing only one is ignored.',
1044
960
  method: 'GET',
1045
961
  pathTemplate: '/partners/time-entries',
1046
- inputSchema: queryInputSchema,
1047
962
  requiresUserAuth: true,
1048
963
  readOnly: true,
1049
964
  destructive: false,
@@ -1054,7 +969,6 @@ const tools = [
1054
969
  description: 'Get a single time entry by external UUID. Soft-deleted entries return 404.',
1055
970
  method: 'GET',
1056
971
  pathTemplate: '/partners/time-entries/{id}',
1057
- inputSchema: idInputSchema,
1058
972
  requiresUserAuth: true,
1059
973
  readOnly: true,
1060
974
  destructive: false,
@@ -1065,7 +979,6 @@ const tools = [
1065
979
  description: 'Create one or more time entry rows. Required body: `peopleUuid` (from `list_people` — must be present and non-empty on create; partner middleware resolves it into the internal `peopleId`) and `timeEntries: [{ date: "YYYY-MM-DD", minutes: <integer>, ... }]` (array of row objects). Per-row optional fields: `description`, `projectUuid` (resolves to the project; setting it auto-fills the matching customer), `customerUuid`, `productServiceUuid`, `billable`, `wage`. Daily cap: the sum of `minutes` across entries on the same calendar day for the same person cannot exceed 24h — the server returns 400 with the remaining-minutes if exceeded. The numeric `peopleId`, `projectId`, `customerId`, `productServiceId`, and `teamId` fields are stripped server-side; use the `*Uuid` variants.',
1066
980
  method: 'POST',
1067
981
  pathTemplate: '/partners/time-entries',
1068
- inputSchema: bodyInputSchema,
1069
982
  requiresUserAuth: true,
1070
983
  readOnly: false,
1071
984
  destructive: false,
@@ -1076,7 +989,6 @@ const tools = [
1076
989
  description: 'Update a time entry by external UUID. Pass only the fields you want to change. `peopleUuid` cannot be cleared on update (passing `null`/`""` returns 400). `projectUuid` / `customerUuid` / `productServiceUuid` each accept a UUID (set) or `null` (clear). Time entries that belong to a processed pay period are read-only and return 400.',
1077
990
  method: 'PATCH',
1078
991
  pathTemplate: '/partners/time-entries/{id}',
1079
- inputSchema: idBodyInputSchema,
1080
992
  requiresUserAuth: true,
1081
993
  readOnly: false,
1082
994
  destructive: false,
@@ -1087,7 +999,6 @@ const tools = [
1087
999
  description: 'Delete a time entry by external UUID (soft delete). Time entries that belong to a processed pay period cannot be deleted and return 400 — adjust the pay period first.',
1088
1000
  method: 'DELETE',
1089
1001
  pathTemplate: '/partners/time-entries/{id}',
1090
- inputSchema: idInputSchema,
1091
1002
  requiresUserAuth: true,
1092
1003
  readOnly: false,
1093
1004
  destructive: true,
@@ -1101,7 +1012,6 @@ const tools = [
1101
1012
  description: 'List expense receipts (pending receipts) for the authenticated COUNT workspace.',
1102
1013
  method: 'GET',
1103
1014
  pathTemplate: '/partners/expense-receipts',
1104
- inputSchema: queryInputSchema,
1105
1015
  requiresUserAuth: true,
1106
1016
  readOnly: true,
1107
1017
  destructive: false,
@@ -1112,7 +1022,6 @@ const tools = [
1112
1022
  description: 'List unmatched expense receipts (pending receipts) for the authenticated COUNT workspace.',
1113
1023
  method: 'GET',
1114
1024
  pathTemplate: '/partners/expense-receipts/unmatched',
1115
- inputSchema: queryInputSchema,
1116
1025
  requiresUserAuth: true,
1117
1026
  readOnly: true,
1118
1027
  destructive: false,
@@ -1123,7 +1032,6 @@ const tools = [
1123
1032
  description: 'Create an expense receipt (pending receipt). MCP forwards JSON only; to attach a receipt image call the partner HTTP API directly as multipart/form-data with field `receipt`. Partner bodies use UUID fields such as accountUuid, categoryAccountUuid, optional expenseReportTypeUuid, vendorUuid, projectUuid, tagUuids, taxUuids — see COUNT Partner API docs. API responses omit receiptUrl by design.',
1124
1033
  method: 'POST',
1125
1034
  pathTemplate: '/partners/expense-receipts',
1126
- inputSchema: bodyInputSchema,
1127
1035
  requiresUserAuth: true,
1128
1036
  readOnly: false,
1129
1037
  destructive: false,
@@ -1134,7 +1042,6 @@ const tools = [
1134
1042
  description: 'Update an expense receipt by external UUID. MCP forwards JSON only; to replace the receipt image use multipart field `receipt` on the partner HTTP API.',
1135
1043
  method: 'PATCH',
1136
1044
  pathTemplate: '/partners/expense-receipts/{id}',
1137
- inputSchema: idBodyInputSchema,
1138
1045
  requiresUserAuth: true,
1139
1046
  readOnly: false,
1140
1047
  destructive: false,
@@ -1145,7 +1052,6 @@ const tools = [
1145
1052
  description: 'Delete an unmatched expense receipt by external UUID.',
1146
1053
  method: 'DELETE',
1147
1054
  pathTemplate: '/partners/expense-receipts/{id}',
1148
- inputSchema: idInputSchema,
1149
1055
  requiresUserAuth: true,
1150
1056
  readOnly: false,
1151
1057
  destructive: true,
@@ -1156,7 +1062,6 @@ const tools = [
1156
1062
  description: 'Match an expense receipt to an expense transaction. Body uses transactionUuid (COUNT transaction external UUID).',
1157
1063
  method: 'POST',
1158
1064
  pathTemplate: '/partners/expense-receipts/{id}/match-manually',
1159
- inputSchema: idBodyInputSchema,
1160
1065
  requiresUserAuth: true,
1161
1066
  readOnly: false,
1162
1067
  destructive: false,
@@ -1167,7 +1072,6 @@ const tools = [
1167
1072
  description: 'Unmatch an expense receipt from its linked transaction.',
1168
1073
  method: 'DELETE',
1169
1074
  pathTemplate: '/partners/expense-receipts/{id}/unmatch',
1170
- inputSchema: idInputSchema,
1171
1075
  requiresUserAuth: true,
1172
1076
  readOnly: false,
1173
1077
  destructive: true,
@@ -1178,7 +1082,6 @@ const tools = [
1178
1082
  description: 'Generate a trial balance for the authenticated workspace. Put filters under `query`: `endDate` (required, YYYY-MM-DD — the as-of date), `startDate` (optional, defaults to fiscal-year start), `currency` (defaults to workspace currency), `transactionStatus` ("all" | "reviewed" | "unreviewed" | "reconciled" | "unreconciled"), `accounts` or `accountUuids` (comma-separated account UUIDs from `list_accounts` — do NOT pass internal numeric ids), `tags` or `tagUuids` (comma-separated tag UUIDs from `list_tags`), `noTags: "true"` (only entries with no tags), `accountType` (one of "Assets", "Liabilities", "Equity", "Income", "Expenses"). This MCP tool is read-only; no `body` is accepted.',
1179
1083
  method: 'POST',
1180
1084
  pathTemplate: '/partners/reports/trial-balance',
1181
- inputSchema: queryInputSchema,
1182
1085
  requiresUserAuth: true,
1183
1086
  readOnly: true,
1184
1087
  destructive: false,
@@ -1189,7 +1092,6 @@ const tools = [
1189
1092
  description: 'Generate a P&L (income statement) for the authenticated workspace. Put filters under `query`: required `startDate` and `endDate` (YYYY-MM-DD); optional `currency`, `transactionStatus` ("all" | "reviewed" | "unreviewed" | "reconciled" | "unreconciled"), `categoryAccount`, `categoryAccountUuid`, or `categoryAccountUuids` (comma-separated account UUIDs from `list_accounts` — use this to filter to one or more income/expense categories such as "Freight & Courier"; do NOT pass internal numeric ids), `tags` or `tagUuids`, `customers` or `customerUuids`, `vendors` or `vendorUuids`, `projects` or `projectUuids`, `products` or `productUuids`, `isPnLByTag: true` (column-per-tag breakdown), `includeNoTag: true`, `reportYear` ("financial" | "calendar"). This MCP tool is read-only; no `body` is accepted. **Sign convention**: revenue rows are returned as positive amounts and expense rows as negative; net profit is the algebraic sum.',
1190
1093
  method: 'POST',
1191
1094
  pathTemplate: '/partners/reports/pnl',
1192
- inputSchema: queryInputSchema,
1193
1095
  requiresUserAuth: true,
1194
1096
  readOnly: true,
1195
1097
  destructive: false,
@@ -1200,7 +1102,6 @@ const tools = [
1200
1102
  description: 'Generate a balance sheet for the authenticated workspace. Put filters under `query`: required `endDate` (YYYY-MM-DD — the as-of date); optional `startDate` (used as the prior-period comparison anchor), `currency`, `transactionStatus`, `accounts` or `accountUuids` (comma-separated account UUIDs from `list_accounts` — do NOT pass internal numeric ids), `tags` or `tagUuids`. This MCP tool is read-only; no `body` is accepted. Returns Assets / Liabilities / Equity sections with subtotals; the report fails to balance only when there are unposted manual entries — those are surfaced in the response payload.',
1201
1103
  method: 'POST',
1202
1104
  pathTemplate: '/partners/reports/balance-sheet',
1203
- inputSchema: queryInputSchema,
1204
1105
  requiresUserAuth: true,
1205
1106
  readOnly: true,
1206
1107
  destructive: false,
@@ -1212,7 +1113,6 @@ const tools = [
1212
1113
  description: 'List P&L budgets for the authenticated workspace. Optional `query.status`: `draft`, `published`, or `archived`. Each budget returns its external UUID as `id`, metadata (name, startPeriod, cadence, actualPeriods, budgetPeriods, currencyCode, status, isOverall), and version summaries (versionNumber, label, isPublished).',
1213
1114
  method: 'GET',
1214
1115
  pathTemplate: '/partners/budgets',
1215
- inputSchema: queryInputSchema,
1216
1116
  requiresUserAuth: true,
1217
1117
  readOnly: true,
1218
1118
  destructive: false,
@@ -1223,7 +1123,6 @@ const tools = [
1223
1123
  description: 'Get the workspace Overall Budget (if configured). Returns the same budget object shape as `list_budgets` rows, or null when no overall budget exists.',
1224
1124
  method: 'GET',
1225
1125
  pathTemplate: '/partners/budgets/overall',
1226
- inputSchema: queryInputSchema,
1227
1126
  requiresUserAuth: true,
1228
1127
  readOnly: true,
1229
1128
  destructive: false,
@@ -1234,7 +1133,6 @@ const tools = [
1234
1133
  description: 'Create a draft budget and seed P&L budget lines for every active Income/Expenses account. Required body: `name`, `startPeriod` (YYYY-MM-DD), `cadence` (`monthly` | `yearly`), `actualPeriods` (>= 0), `budgetPeriods` (>= 1). Optional: `currencyCode` (defaults to workspace currency), `isOverall` (boolean — only one overall budget per workspace). Returns `{ budget, version }` with the initial versionNumber (usually 1). After create, import amounts with `bulk_update_budget_cells`.',
1235
1134
  method: 'POST',
1236
1135
  pathTemplate: '/partners/budgets',
1237
- inputSchema: bodyInputSchema,
1238
1136
  requiresUserAuth: true,
1239
1137
  readOnly: false,
1240
1138
  destructive: false,
@@ -1245,7 +1143,6 @@ const tools = [
1245
1143
  description: 'Get a single budget by external UUID, including version summaries.',
1246
1144
  method: 'GET',
1247
1145
  pathTemplate: '/partners/budgets/{id}',
1248
- inputSchema: idInputSchema,
1249
1146
  requiresUserAuth: true,
1250
1147
  readOnly: true,
1251
1148
  destructive: false,
@@ -1256,7 +1153,6 @@ const tools = [
1256
1153
  description: 'Update draft budget metadata by external UUID. Pass only fields to change: `name`, `startPeriod`, `cadence`, `actualPeriods`, `budgetPeriods`, `isOverall`. Rejected when the budget is published or locked.',
1257
1154
  method: 'PATCH',
1258
1155
  pathTemplate: '/partners/budgets/{id}',
1259
- inputSchema: idBodyInputSchema,
1260
1156
  requiresUserAuth: true,
1261
1157
  readOnly: false,
1262
1158
  destructive: false,
@@ -1267,7 +1163,6 @@ const tools = [
1267
1163
  description: 'Export a budget grid for Excel round-trips. Returns `columns` (actual + budget period labels) and `rows` with `accountUuid`, `accountName`, `accountPath`, and per-period `values`. Optional `query.versionNumber` (defaults to latest version), `query.reportType` (`accrual` | `cash`), and `query.includeActuals` (`false` skips journal-entry actuals for faster budget-only export). Use `list_accounts` / `resolve_references` to map account names before re-importing edited rows.',
1268
1164
  method: 'GET',
1269
1165
  pathTemplate: '/partners/budgets/{id}/grid',
1270
- inputSchema: idInputSchema,
1271
1166
  requiresUserAuth: true,
1272
1167
  readOnly: true,
1273
1168
  destructive: false,
@@ -1278,7 +1173,6 @@ const tools = [
1278
1173
  description: 'List version summaries for a budget: `versionNumber`, `label`, `isPublished`, `createdAt`. Use `versionNumber` on cell update tools.',
1279
1174
  method: 'GET',
1280
1175
  pathTemplate: '/partners/budgets/{id}/versions',
1281
- inputSchema: idInputSchema,
1282
1176
  requiresUserAuth: true,
1283
1177
  readOnly: true,
1284
1178
  destructive: false,
@@ -1289,7 +1183,6 @@ const tools = [
1289
1183
  description: 'Atomically update up to 100 budget cell amounts on a **draft** version. Requires `id` (budget UUID) and `versionNumber`. Body: `{ updates: [{ accountUuid, periodStart (YYYY-MM-DD), amount }] }`. Each row must reference a P&L account UUID from `list_accounts` and a budget column periodStart from `get_budget_grid`. All rows succeed or fail together.',
1290
1184
  method: 'PATCH',
1291
1185
  pathTemplate: '/partners/budgets/{id}/versions/{versionNumber}/cells',
1292
- inputSchema: idVersionNumberBodyInputSchema,
1293
1186
  requiresUserAuth: true,
1294
1187
  readOnly: false,
1295
1188
  destructive: false,
@@ -1300,7 +1193,6 @@ const tools = [
1300
1193
  description: 'Import budget amounts with partial-success semantics (recommended for spreadsheet imports). Same row shape as `update_budget_cells`: `{ updates: [{ accountUuid, periodStart, amount }, ...] }`. **Cap**: 100 rows per call; **recommended batch size**: ~25. Response: `{ successCount, errorCount, results: [{ index, success, budgetCellUpdate? | error }] }`. HTTP status is `201` when the batch is accepted even if some rows fail — retry only failed indices.',
1301
1194
  method: 'POST',
1302
1195
  pathTemplate: '/partners/budgets/{id}/versions/{versionNumber}/cells/bulk',
1303
- inputSchema: idVersionNumberBodyInputSchema,
1304
1196
  requiresUserAuth: true,
1305
1197
  readOnly: false,
1306
1198
  destructive: false,
@@ -1311,7 +1203,6 @@ const tools = [
1311
1203
  description: 'Create a new draft budget version (copies lines and cell values from the previous version). Optional body: `label`. Returns the new version with an incremented `versionNumber`.',
1312
1204
  method: 'POST',
1313
1205
  pathTemplate: '/partners/budgets/{id}/versions',
1314
- inputSchema: idOptionalBodyInputSchema,
1315
1206
  requiresUserAuth: true,
1316
1207
  readOnly: false,
1317
1208
  destructive: false,
@@ -1322,7 +1213,6 @@ const tools = [
1322
1213
  description: 'Publish a budget version and set budget status to `published`. Optional body: `versionNumber` (defaults to latest draft). Rejects cell edits on published versions.',
1323
1214
  method: 'POST',
1324
1215
  pathTemplate: '/partners/budgets/{id}/publish',
1325
- inputSchema: idOptionalBodyInputSchema,
1326
1216
  requiresUserAuth: true,
1327
1217
  readOnly: false,
1328
1218
  destructive: false,
@@ -1333,7 +1223,6 @@ const tools = [
1333
1223
  description: 'Archive a budget by external UUID. Cannot archive the Overall Budget.',
1334
1224
  method: 'POST',
1335
1225
  pathTemplate: '/partners/budgets/{id}/archive',
1336
- inputSchema: idOptionalBodyInputSchema,
1337
1226
  requiresUserAuth: true,
1338
1227
  readOnly: false,
1339
1228
  destructive: true,
@@ -1344,7 +1233,6 @@ const tools = [
1344
1233
  description: 'Duplicate a budget into a new draft. Required body: `newName`. Optional: `carryValues` (copy cell amounts), `isOverall`, `versionNumber` (source version; defaults to latest).',
1345
1234
  method: 'POST',
1346
1235
  pathTemplate: '/partners/budgets/{id}/duplicate',
1347
- inputSchema: idBodyInputSchema,
1348
1236
  requiresUserAuth: true,
1349
1237
  readOnly: false,
1350
1238
  destructive: false,
@@ -1365,12 +1253,15 @@ const tools = [
1365
1253
  'Optional `query.include` is a comma-separated list of blocks to compute (omit any block you do not need): `workspace,cash,profitability,receivables,payables,taxObligations,connections`. Omitting `include` returns every block. This is the cheapest single call to populate an end-of-period dashboard or a "how is the business doing right now" summary.',
1366
1254
  method: 'GET',
1367
1255
  pathTemplate: '/partners/workspace-stats',
1368
- inputSchema: queryInputSchema,
1369
1256
  requiresUserAuth: true,
1370
1257
  readOnly: true,
1371
1258
  destructive: false,
1372
1259
  },
1373
1260
  ];
1261
+ const tools = rawTools.map((_tool) => ({
1262
+ ..._tool,
1263
+ inputSchema: toolInputSchemas.getToolInputSchema({ toolName: _tool.name }),
1264
+ }));
1374
1265
  export const toolDefinitions = tools;
1375
1266
  export function getToolDefinition(params) {
1376
1267
  const { toolName } = params;