@stubbedev/trimit-mcp 0.1.2 → 0.1.4

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
@@ -1,205 +1,197 @@
1
- # TRIMIT Dev MCP
1
+ # trimit-mcp
2
2
 
3
- An MCP server that helps you construct and validate [TRIMIT API](https://apidocs.trimit.com/) calls — the fashion/apparel ERP extension layered on Microsoft Dynamics 365 Business Central. No authentication required in the server itself.
3
+ MCP server that generates request templates for the [TRIMIT API](https://apidocs.trimit.com/) — the fashion/apparel ERP extension on Microsoft Dynamics 365 Business Central.
4
4
 
5
- Tools are loaded on demand by resource category. Ask about sales orders and the salesdocs tools appear. Ask about masters and the products tools appear. The server starts lean and grows with your needs, and always knows the OAuth setup, URL templating, and OData conventions for every operation.
5
+ Constructs the URL, method, headers, OData query string, body, and an OAuth-ready `fetch` snippet for every documented endpoint. Does **not** execute requests and holds no credentials.
6
6
 
7
- ## What it does
7
+ - 47 endpoint tools across 9 resource categories, lazy-loaded on demand
8
+ - 17 always-on tools: search, category management, `$batch` builder, conceptual reference (`trimit_explain_*`), spec validators (`trimit_describe_entity`, `trimit_example_payload`, `trimit_validate_request`, `trimit_check_odata`, `trimit_lint_snippet`)
9
+ - MCP resources: `trimit://entity/{name}`, `trimit://enum/{name}`, `trimit://entities`, `trimit://enums`, `trimit://categories`
10
+ - MCP prompts: `review_sales_order_post`, `add_idempotent_export_loop`
11
+ - Zod input schemas; OData v4 (`$filter`/`$select`/`$expand`/`$orderby`/`$top`/`$skip`/`$count`) built-in
12
+ - Handles ETag (`If-Match`), `IEEE754Compatible` decimals, `@odata.nextLink` paging, `$batch` envelopes
13
+ - TRIMIT integration path `api/trimit/integration/v1.1`, standard BC path `api/v2.0`, OData v4 at `/ODataV4`
8
14
 
9
- - Constructs valid TRIMIT / Business Central API request URLs, methods, headers, and bodies
10
- - Validates required and optional parameters via Zod schemas
11
- - Returns the OAuth 2.0 / Microsoft Entra setup for every operation
12
- - Returns ready-to-use fetch code examples
13
- - Links to official documentation
14
- - Explains cross-cutting concepts: OAuth auth, base URL templating, OData queries, ETag concurrency, `$batch`, paging, sales document lifecycle
15
- - Loads resource categories on demand — only what you need
15
+ ## Install
16
16
 
17
- ## Tools
17
+ `npx -y @stubbedev/trimit-mcp` (stdio transport).
18
18
 
19
- ### Always available
19
+ ### Claude Code
20
20
 
21
- These tools are loaded immediately — no setup required.
22
-
23
- | Tool | Description |
24
- |---|---|
25
- | `list_categories` | List all resource categories and which are currently loaded |
26
- | `load_category` | Load tools for a category; triggers `tools/list_changed` |
27
- | `search_trimit_api` | Search TRIMIT API endpoints by keyword; returns a `suggestedCategory` to load |
28
- | `trimit_build_batch` | Build a valid `/$batch` request body from any number of operations |
29
- | `trimit_explain_auth` | OAuth 2.0 client credentials against Microsoft Entra |
30
- | `trimit_explain_base_urls` | Base URL structure; `{tenant}`, `{environment}`, `{companyId}` substitution |
31
- | `trimit_explain_odata` | `$filter`, `$select`, `$expand`, `$orderby`, `$top`, `$count` with examples |
32
- | `trimit_explain_paging` | `@odata.nextLink` iteration and the BC 20 000-row page cap |
33
- | `trimit_explain_concurrency` | `@odata.etag`, `If-Match`, `IEEE754Compatible` decimals |
34
- | `trimit_explain_batch` | OData `$batch` envelope, `dependsOn`, response matching |
35
- | `trimit_explain_doc_lifecycle` | Sales Import Journal → processed → posted; `exportedDocuments` markers |
36
- | `trimit_explain_errors` | 400 / 401 / 403 / 404 / 409 / 412 / 429 and BC error codes |
37
-
38
- ### Resource categories (loaded on demand)
39
-
40
- | Category | Tools | What you get |
41
- |---|---|---|
42
- | **standard** | 8 | Microsoft BC standard API: companies (incl. ODataV4 view), items, customers (GET + PATCH defaultDimensions), `$metadata` (API + ODataV4), entityDefinitions |
43
- | **masterdata** | 8 | Campaigns, customerPriceGroups, priceGroupParameters, VarDimCombinations, vardimtypes, vardimtypevalues, itemAttributes, collections |
44
- | **products** | 4 | TRIMIT Masters (style headers), Items (SKUs), Products feed, Categories — all with full expand defaults |
45
- | **inventory** | 2 | Locations, Inventories (incl. Future Delivers) |
46
- | **customers** | 5 | TRIMIT-enriched customers list, contacts, salespersons, POST customer, PATCH customer (additionalFields, If-Match, IEEE754Compatible) |
47
- | **salesdocs** | 11 | Quote / Order / Invoice / Credit Memo / Blanket Order / Return Order — list, list-processed, get-by-systemId, typed lists (orders/invoices/credit memos/return orders), POST return order, POST sales document (create + add-fields), `$batch` bulk insert |
48
- | **postedsales** | 5 | Posted documents (union), posted invoices, posted credit memos, posted return receipts, posted shipments (with tracking lines) |
49
- | **exported** | 3 | GET / POST / DELETE `exportedDocuments` markers — exclude already-processed docs from future polls |
50
- | **metadata** | 1 | TRIMIT integration API `$metadata` (EDMX) for codegen |
51
-
52
- ### Example tool output
53
-
54
- Every tool returns a structured object with the full request details and auth requirements:
55
-
56
- ```json
57
- {
58
- "endpoint": "https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/trimit/integration/v1.1/companies({companyId})/masters?$expand=masterDescriptions,masterDefDims,...",
59
- "method": "GET",
60
- "headers": {
61
- "Authorization": "Bearer {token}",
62
- "Content-Type": "application/json",
63
- "Accept": "application/json"
64
- },
65
- "pathParams": { "tenant": "{tenant}", "environment": "{environment}", "companyId": "{companyId}" },
66
- "queryParams": { "$expand": "masterDescriptions,masterDefDims,..." },
67
- "body": null,
68
- "description": "TRIMIT Masters (style headers).",
69
- "docsUrl": "https://apidocs.trimit.com/",
70
- "codeExample": "const response = await fetch('...', { method: 'GET', headers: { Authorization: 'Bearer {token}' } });\nconst data = await response.json();",
71
- "auth": {
72
- "type": "OAuth 2.0 — Microsoft Entra (Azure AD) client credentials",
73
- "tokenEndpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
74
- "scope": "https://api.businesscentral.dynamics.com/.default",
75
- "notes": "Customer-tenant Entra app registration with Business Central / TRIMIT integration consent."
76
- },
77
- "notes": "Key fields: number (master code), noSystem (SKU template), masterItems → SKU variants. Heavy payload — narrow with $select and trimmed $expand for production traffic."
78
- }
79
- ```
80
-
81
- ---
82
-
83
- ## Installation
84
-
85
- The recommended way to run this server is via `npx` — no local install needed.
86
-
87
- ```
88
- npx -y @stubbedev/trimit-mcp
21
+ ```bash
22
+ claude mcp add trimit-dev -- npx -y @stubbedev/trimit-mcp
89
23
  ```
90
24
 
91
- ### Claude Desktop
92
-
93
- Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
25
+ Or `.mcp.json`:
94
26
 
95
27
  ```json
96
28
  {
97
29
  "mcpServers": {
98
- "trimit-dev": {
99
- "command": "npx",
100
- "args": ["-y", "@stubbedev/trimit-mcp"]
101
- }
30
+ "trimit-dev": { "command": "npx", "args": ["-y", "@stubbedev/trimit-mcp"] }
102
31
  }
103
32
  }
104
33
  ```
105
34
 
106
- ### Claude Code (CLI)
107
-
108
- ```bash
109
- claude mcp add trimit-dev -- npx -y @stubbedev/trimit-mcp
110
- ```
35
+ ### Claude Desktop
111
36
 
112
- Or add to your project's `.mcp.json`:
37
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
113
38
 
114
39
  ```json
115
40
  {
116
41
  "mcpServers": {
117
- "trimit-dev": {
118
- "command": "npx",
119
- "args": ["-y", "@stubbedev/trimit-mcp"]
120
- }
42
+ "trimit-dev": { "command": "npx", "args": ["-y", "@stubbedev/trimit-mcp"] }
121
43
  }
122
44
  }
123
45
  ```
124
46
 
125
47
  ### Cursor
126
48
 
127
- Open **Settings → MCP** and add a new server, or edit `~/.cursor/mcp.json`:
49
+ `~/.cursor/mcp.json`:
128
50
 
129
51
  ```json
130
52
  {
131
53
  "mcpServers": {
132
- "trimit-dev": {
133
- "command": "npx",
134
- "args": ["-y", "@stubbedev/trimit-mcp"]
135
- }
54
+ "trimit-dev": { "command": "npx", "args": ["-y", "@stubbedev/trimit-mcp"] }
136
55
  }
137
56
  }
138
57
  ```
139
58
 
140
59
  ### Windsurf
141
60
 
142
- Edit `~/.codeium/windsurf/mcp_config.json`:
61
+ `~/.codeium/windsurf/mcp_config.json`:
143
62
 
144
63
  ```json
145
64
  {
146
65
  "mcpServers": {
147
- "trimit-dev": {
148
- "command": "npx",
149
- "args": ["-y", "@stubbedev/trimit-mcp"]
150
- }
66
+ "trimit-dev": { "command": "npx", "args": ["-y", "@stubbedev/trimit-mcp"] }
151
67
  }
152
68
  }
153
69
  ```
154
70
 
155
71
  ### Zed
156
72
 
157
- Edit your `settings.json` (open via **Zed → Settings → Open Settings**):
73
+ `settings.json`:
158
74
 
159
75
  ```json
160
76
  {
161
77
  "context_servers": {
162
- "trimit-dev": {
163
- "command": {
164
- "path": "npx",
165
- "args": ["-y", "@stubbedev/trimit-mcp"]
166
- }
167
- }
78
+ "trimit-dev": { "command": { "path": "npx", "args": ["-y", "@stubbedev/trimit-mcp"] } }
168
79
  }
169
80
  }
170
81
  ```
171
82
 
172
83
  ### OpenCode
173
84
 
174
- Edit `~/.config/opencode/config.json`:
85
+ `~/.config/opencode/config.json`:
175
86
 
176
87
  ```json
177
88
  {
178
89
  "mcp": {
179
- "trimit-dev": {
180
- "type": "local",
181
- "command": ["npx", "-y", "@stubbedev/trimit-mcp"]
182
- }
90
+ "trimit-dev": { "type": "local", "command": ["npx", "-y", "@stubbedev/trimit-mcp"] }
183
91
  }
184
92
  }
185
93
  ```
186
94
 
187
95
  ### Codex (OpenAI)
188
96
 
189
- Edit `~/.codex/config.json`:
97
+ `~/.codex/config.toml`:
190
98
 
191
- ```json
99
+ ```toml
100
+ [mcp_servers.trimit-dev]
101
+ command = "npx"
102
+ args = ["-y", "@stubbedev/trimit-mcp"]
103
+ ```
104
+
105
+ ## Architecture
106
+
107
+ Bootstrap tools are registered at startup. Endpoint tools register only after `load_category` is called for their category — each call emits `notifications/tools/list_changed`. This keeps the active tool surface small (~12 tools idle, +N per loaded category).
108
+
109
+ Every endpoint tool returns:
110
+
111
+ ```ts
192
112
  {
193
- "mcpServers": {
194
- "trimit-dev": {
195
- "command": "npx",
196
- "args": ["-y", "@stubbedev/trimit-mcp"]
197
- }
198
- }
113
+ endpoint: string; // fully-qualified URL with {tenant}/{environment}/{companyId} placeholders
114
+ method: "GET" | "POST" | "PATCH" | "DELETE";
115
+ headers: Record<string, string>;
116
+ pathParams: Record<string, string>;
117
+ queryParams: Record<string, string>; // OData $-params
118
+ body: unknown | null;
119
+ description: string;
120
+ docsUrl: string;
121
+ codeExample: string; // ready-to-paste fetch() snippet
122
+ auth: { type, tokenEndpoint, scope, notes };
123
+ notes: string | null; // gotchas (read-replica hints, case-sensitivity, etc.)
199
124
  }
200
125
  ```
201
126
 
202
- ---
127
+ ## Bootstrap tools
128
+
129
+ | Tool | Purpose |
130
+ |---|---|
131
+ | `list_categories` | Enumerate categories + loaded state + tool count |
132
+ | `load_category` | Register tools for a category, emit `tools/list_changed` |
133
+ | `search_trimit_api` | Keyword → endpoint match with `suggestedCategory` |
134
+ | `trimit_build_batch` | Build `/$batch` envelope (validates method, `dependsOn`, headers) |
135
+ | `trimit_explain_auth` | OAuth 2.0 client credentials + Entra app prerequisites |
136
+ | `trimit_explain_base_urls` | URL anatomy + placeholder resolution |
137
+ | `trimit_explain_odata` | Query parameter reference w/ examples |
138
+ | `trimit_explain_paging` | `@odata.nextLink` pattern; 20 000-row BC page cap |
139
+ | `trimit_explain_concurrency` | `@odata.etag` / `If-Match` / `IEEE754Compatible` |
140
+ | `trimit_explain_batch` | `$batch` envelope spec + response matching |
141
+ | `trimit_explain_doc_lifecycle` | Sales Import Journal → processed → posted; `exportedDocuments` markers |
142
+ | `trimit_explain_errors` | 400/401/403/404/409/412/429 + BC error codes |
143
+ | `trimit_describe_entity` | Field schema for an entity (types, required/mutable, enum refs, decimals, nav props, default `$expand`) |
144
+ | `trimit_example_payload` | Generate minimal/full POST or PATCH body template for an entity |
145
+ | `trimit_validate_request` | Validate `{endpoint, method, headers, body}` against the spec — issues + fix suggestions |
146
+ | `trimit_check_odata` | Validate `$filter` / `$select` / `$expand` / `$orderby` / `$top` / `$skip` |
147
+ | `trimit_lint_snippet` | Lint user integration code for BC pitfalls (no `If-Match`, no `nextLink`, hardcoded token, missing IEEE754, doctype casing, `=` vs `eq`, etc.) |
148
+
149
+ ## Resources
150
+
151
+ | URI | Returns |
152
+ |---|---|
153
+ | `trimit://entities` | Catalog of all entities |
154
+ | `trimit://entity/{name}` | Schema for one entity (URI completion supported) |
155
+ | `trimit://enums` | Catalog of all enums |
156
+ | `trimit://enum/{name}` | Allowed values for one enum |
157
+ | `trimit://categories` | Tool category catalog with load state |
158
+
159
+ ## Prompts
160
+
161
+ | Name | Produces |
162
+ |---|---|
163
+ | `review_sales_order_post` | Audit a `/salesDocuments` POST body + headers against the spec |
164
+ | `add_idempotent_export_loop` | Refactor a poller to use `/exportedDocuments` markers |
165
+
166
+ ## Categories
167
+
168
+ | Category | N | Endpoints |
169
+ |---|---|---|
170
+ | `standard` | 8 | `companies`, `companies` (OData v4), `items`, `customers` (GET + PATCH `defaultDimensions` w/ `If-Match`), `$metadata` (REST + OData), `entityDefinitions` |
171
+ | `masterdata` | 8 | `campaigns`, `customerPriceGroups`, `priceGroupParameters`, `VarDimCombinations`, `vardimtypes`, `vardimtypevalues`, `itemAttributes`, `collections` |
172
+ | `products` | 4 | `masters`, `items`, `products`, `categories` (sensible default `$expand`) |
173
+ | `inventory` | 2 | `locations`, `inventories` (Future Delivers when enabled) |
174
+ | `customers` | 5 | TRIMIT `customers` list, `contacts`, `salespersons`, POST customer, PATCH customer (`additionalFields`, `If-Match`, `IEEE754Compatible`) |
175
+ | `salesdocs` | 11 | `salesDocuments` list/processed/by-systemId, typed lists (`salesOrders`/`salesInvoices`/`salesCreditMemos`/`salesReturnOrders`), POST return order, POST sales doc (create + append), `/$batch` |
176
+ | `postedsales` | 5 | `postedSalesDocuments`, `postedSalesInvoices`, `postedSalesCreditMemos`, `postedSalesReturnReceipts`, `postedSalesShipments` (w/ `trackingLines`) |
177
+ | `exported` | 3 | `exportedDocuments` GET/POST/DELETE |
178
+ | `metadata` | 1 | TRIMIT `$metadata` EDMX |
179
+
180
+ ## Auth
181
+
182
+ OAuth 2.0 client credentials against Microsoft Entra:
183
+
184
+ ```
185
+ POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
186
+ grant_type=client_credentials
187
+ &client_id={clientId}
188
+ &client_secret={clientSecret}
189
+ &scope=https://api.businesscentral.dynamics.com/.default
190
+ ```
191
+
192
+ Send `Authorization: Bearer {token}` on every call. Tokens ~1h. Requires an Entra app registration on the customer tenant, admin consent, and a matching BC permission set (e.g. `D365 BUS PREMIUM`).
193
+
194
+ Placeholders to substitute before issuing requests: `{tenant}` (Entra GUID), `{environment}` (`Production` / `Sandbox` / custom), `{companyId}` (BC company GUID — resolve via `trimit_std_get_companies`).
203
195
 
204
196
  ## Development
205
197
 
@@ -207,24 +199,50 @@ Edit `~/.codex/config.json`:
207
199
  git clone https://github.com/stubbedev/trimit-dev-mcp.git
208
200
  cd trimit-dev-mcp
209
201
  npm install
210
- npm run build
211
- npm start
202
+ npm run build # tsc → dist/
203
+ npm run dev # tsx watch
204
+ npm start # node dist/index.js
212
205
  ```
213
206
 
214
- For live reload during development:
207
+ Smoke check (boots server, validates `tools/list` response, asserts non-empty + well-formed):
215
208
 
216
209
  ```bash
217
- npm run dev
210
+ npm run smoke:validate
218
211
  ```
219
212
 
220
- ### Test with MCP Inspector
213
+ Interactive debugging:
221
214
 
222
215
  ```bash
223
216
  npx @modelcontextprotocol/inspector npx -y @stubbedev/trimit-mcp
224
217
  ```
225
218
 
226
- ---
219
+ Release (runs `preversion` → build + smoke, then tags, pushes, creates GitHub release):
220
+
221
+ ```bash
222
+ npm run release:patch # | release:minor | release:major
223
+ ```
224
+
225
+ Layout:
226
+
227
+ ```
228
+ src/
229
+ index.ts # entry
230
+ server.ts # McpServer + bootstrap tools
231
+ registry.ts # ToolRegistry (per-category load/unload)
232
+ common.ts # base URLs, buildOdataQuery, fetchExample, TRIMIT_AUTH
233
+ categories/ # one file per category, exports ToolDefinition[]
234
+ spec/
235
+ types.ts # Entity/Field/EnumDef types
236
+ enums.ts # Static enum catalog (docType, customerType, salesLineType, ...)
237
+ entities.ts # Entity registry — fields/keys/nav props/defaultExpand
238
+ payloads.ts # buildPayload(entity, op, shape) → request body templates
239
+ odata.ts # $filter/$select/$expand/$orderby validators
240
+ validate.ts # validateRequest({endpoint, method, headers, body})
241
+ lint.ts # lintSnippet(code) — BC pitfall heuristics
242
+ ```
243
+
244
+ Add an endpoint: append a `ToolDefinition` to the relevant `categories/*.ts`, re-export if new category, add a `CATEGORY_TOOL_MAP` entry + description in `server.ts`.
227
245
 
228
246
  ## License
229
247
 
230
- MIT
248
+ [MIT](./LICENSE)
@@ -105,7 +105,7 @@ export const customersTools = [
105
105
  zodShape: {
106
106
  number: z.string().describe("Customer number (unique)"),
107
107
  displayName: z.string(),
108
- type: z.string().describe("e.g. 'Company' or 'Person'"),
108
+ type: z.enum(["Company", "Person"]).describe("Case-sensitive."),
109
109
  addressLine1: z.string(),
110
110
  city: z.string(),
111
111
  country: z.string().describe("Country/region code (ISO)"),
@@ -118,8 +118,8 @@ export const customersTools = [
118
118
  salespersonCode: z.string().optional(),
119
119
  salespersonCode2: z.string().optional(),
120
120
  salespersonCode3: z.string().optional(),
121
- creditLimit: z.number().optional(),
122
- blocked: z.string().optional().describe(" ' ', 'Ship', 'Invoice', 'All'"),
121
+ creditLimit: z.number().optional().describe("Decimal — send as string when IEEE754Compatible: true."),
122
+ blocked: z.enum([" ", "Ship", "Invoice", "All"]).optional().describe("Space = not blocked. Case-sensitive."),
123
123
  chain: z.string().optional(),
124
124
  companyGroup: z.string().optional(),
125
125
  selltoGroup: z.string().optional(),
@@ -2,6 +2,9 @@ import { z } from "zod";
2
2
  import { TRIMIT_AUTH, buildHeaders, buildOdataQuery, fetchExample, trimitBase } from "../common.js";
3
3
  const CATEGORY = "salesdocs";
4
4
  const DOC_TYPES = ["Quote", "Order", "Invoice", "Credit Memo", "Blanket Order", "Return Order"];
5
+ const SALES_LINE_TYPES = ["Item", "G/L Account", "Resource", "Fixed Asset", "Charge (Item)", "Comment"];
6
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
7
+ const ISO_DATE_MSG = "YYYY-MM-DD";
5
8
  export const salesdocsTools = [
6
9
  {
7
10
  name: "trimit_salesdocs_list",
@@ -221,18 +224,18 @@ export const salesdocsTools = [
221
224
  SellToCustomerName: z.string().optional(),
222
225
  sellToEmail: z.string().optional(),
223
226
  selltoPhoneNo: z.string().optional(),
224
- orderDate: z.string().describe("YYYY-MM-DD"),
227
+ orderDate: z.string().regex(ISO_DATE, ISO_DATE_MSG),
225
228
  releaseDocument: z.boolean().optional(),
226
229
  additionalFields: z
227
230
  .array(z.object({ number: z.string().optional(), name: z.string(), value: z.string() }))
228
231
  .optional(),
229
232
  salesReturnOrderLines: z
230
233
  .array(z.object({
231
- lineNo: z.number(),
232
- type: z.string().describe("Usually 'Item'"),
234
+ lineNo: z.number().int(),
235
+ type: z.enum(SALES_LINE_TYPES).describe("Usually 'Item'. Case-sensitive."),
233
236
  no: z.string().describe("Item number"),
234
- unitPrice: z.number(),
235
- quantity: z.number(),
237
+ unitPrice: z.number().describe("Decimal — string when IEEE754Compatible: true."),
238
+ quantity: z.number().describe("Decimal — string when IEEE754Compatible: true."),
236
239
  locationCode: z.string().optional(),
237
240
  additionalFields: z
238
241
  .array(z.object({ name: z.string(), value: z.string() }))
@@ -268,22 +271,22 @@ export const salesdocsTools = [
268
271
  docType: z.enum(DOC_TYPES),
269
272
  docNo: z.string(),
270
273
  SellToCustomerName: z.string().optional(),
271
- orderDate: z.string().optional().describe("YYYY-MM-DD"),
274
+ orderDate: z.string().regex(ISO_DATE, ISO_DATE_MSG).optional(),
272
275
  releaseDocument: z.boolean().optional(),
273
276
  additionalFields: z
274
277
  .array(z.object({ name: z.string(), value: z.string() }))
275
278
  .optional(),
276
279
  salesDocumentLines: z
277
280
  .array(z.object({
278
- lineNo: z.number().optional(),
279
- type: z.string().describe("'Item', 'G/L Account', etc."),
281
+ lineNo: z.number().int().optional(),
282
+ type: z.enum(SALES_LINE_TYPES).describe("Case-sensitive."),
280
283
  no: z.string(),
281
- unitPrice: z.number().optional(),
282
- quantity: z.number().optional(),
284
+ unitPrice: z.number().optional().describe("Decimal — string when IEEE754Compatible: true."),
285
+ quantity: z.number().optional().describe("Decimal — string when IEEE754Compatible: true."),
283
286
  unitOfMeasureCode: z.string().optional(),
284
287
  locationCode: z.string().optional(),
285
288
  periodCode: z.string().optional(),
286
- discountAmount: z.number().optional(),
289
+ discountAmount: z.number().optional().describe("Decimal — string when IEEE754Compatible: true."),
287
290
  additionalFields: z.array(z.object({ name: z.string(), value: z.string() })).optional(),
288
291
  }))
289
292
  .optional(),
@@ -314,21 +317,21 @@ export const salesdocsTools = [
314
317
  docType: z.enum(DOC_TYPES),
315
318
  docNo: z.string().describe("Customer-facing document number (unique per docType)"),
316
319
  sellToCustomerNo: z.string().describe("BC customer number, e.g. '10000'"),
317
- orderDate: z.string().describe("YYYY-MM-DD"),
320
+ orderDate: z.string().regex(ISO_DATE, ISO_DATE_MSG),
318
321
  releaseDocument: z.boolean().optional().describe("Release the doc in BC on create."),
319
322
  additionalFields: z
320
323
  .array(z.object({ name: z.string(), value: z.string() }))
321
324
  .optional(),
322
325
  salesDocumentLines: z
323
326
  .array(z.object({
324
- type: z.string().describe("'Item', 'G/L Account', 'Resource', 'Fixed Asset', 'Charge (Item)'"),
327
+ type: z.enum(SALES_LINE_TYPES).describe("Case-sensitive."),
325
328
  no: z.string().describe("Item number / G/L account / etc."),
326
- unitPrice: z.number(),
327
- quantity: z.number(),
329
+ unitPrice: z.number().describe("Decimal — string when IEEE754Compatible: true."),
330
+ quantity: z.number().describe("Decimal — string when IEEE754Compatible: true."),
328
331
  unitOfMeasureCode: z.string().optional(),
329
332
  locationCode: z.string().optional(),
330
333
  periodCode: z.string().optional(),
331
- discountAmount: z.number().optional(),
334
+ discountAmount: z.number().optional().describe("Decimal — string when IEEE754Compatible: true."),
332
335
  additionalFields: z
333
336
  .array(z.object({ name: z.string(), value: z.string() }))
334
337
  .optional(),