@assetlab/mcp-server 1.19.7 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -20
- package/dist/client.js +13 -9
- package/dist/client.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth.js +12 -3
- package/dist/oauth.js.map +1 -1
- package/dist/response-shaping.d.ts +28 -0
- package/dist/response-shaping.js +62 -0
- package/dist/response-shaping.js.map +1 -0
- package/dist/tools-write.js +1154 -257
- package/dist/tools-write.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +397 -227
- package/dist/tools.js.map +1 -1
- package/dist/worker.js +15 -3
- package/dist/worker.js.map +1 -1
- package/package.json +44 -46
package/dist/tools-write.js
CHANGED
|
@@ -6,16 +6,10 @@
|
|
|
6
6
|
* Write tools require API keys with the appropriate :write scope.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
import { formatError, formatResult } from './response-shaping.js';
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Helpers
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
|
-
function formatResult(data) {
|
|
13
|
-
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
14
|
-
}
|
|
15
|
-
function formatError(err) {
|
|
16
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
17
|
-
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
18
|
-
}
|
|
19
13
|
/** Strip undefined values so the API only receives explicitly-set fields. */
|
|
20
14
|
function buildBody(params) {
|
|
21
15
|
const body = {};
|
|
@@ -48,8 +42,8 @@ function coerceNumber(value) {
|
|
|
48
42
|
function normalizePmTemplateBody(params) {
|
|
49
43
|
const out = { ...params };
|
|
50
44
|
if (Array.isArray(out.tasks)) {
|
|
51
|
-
out.tasks = out.tasks.map(
|
|
52
|
-
const t =
|
|
45
|
+
out.tasks = out.tasks.map(raw => {
|
|
46
|
+
const t = raw && typeof raw === 'object' ? raw : {};
|
|
53
47
|
const id = typeof t.id === 'string' && t.id.trim() !== '' ? t.id : randomTaskId();
|
|
54
48
|
const description = typeof t.description === 'string' ? t.description : '';
|
|
55
49
|
const completed = typeof t.completed === 'boolean' ? t.completed : false;
|
|
@@ -57,8 +51,8 @@ function normalizePmTemplateBody(params) {
|
|
|
57
51
|
});
|
|
58
52
|
}
|
|
59
53
|
if (Array.isArray(out.resources)) {
|
|
60
|
-
out.resources = out.resources.map(
|
|
61
|
-
const r =
|
|
54
|
+
out.resources = out.resources.map(raw => {
|
|
55
|
+
const r = raw && typeof raw === 'object' ? raw : {};
|
|
62
56
|
const res = {};
|
|
63
57
|
if (typeof r.name === 'string')
|
|
64
58
|
res.name = r.name;
|
|
@@ -89,11 +83,22 @@ export function registerWriteTools(server, client) {
|
|
|
89
83
|
title: z.string().min(1).max(500).describe('Work order title (required)'),
|
|
90
84
|
description: z.string().optional().describe('Detailed description'),
|
|
91
85
|
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional().describe('Priority level'),
|
|
92
|
-
status: z
|
|
86
|
+
status: z
|
|
87
|
+
.enum(['NEW', 'IN_PROGRESS', 'ON_HOLD', 'REJECTED', 'COMPLETED', 'CANCELLED'])
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Status'),
|
|
93
90
|
type: z.enum(['PM', 'REACTIVE']).optional().describe('Work order type'),
|
|
94
91
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
95
|
-
building_id: z
|
|
96
|
-
|
|
92
|
+
building_id: z
|
|
93
|
+
.string()
|
|
94
|
+
.uuid()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
97
|
+
location_id: z
|
|
98
|
+
.string()
|
|
99
|
+
.uuid()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
97
102
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
98
103
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
99
104
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
@@ -101,9 +106,20 @@ export function registerWriteTools(server, client) {
|
|
|
101
106
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
102
107
|
work_category_id: z.string().uuid().optional().describe('Work category ID'),
|
|
103
108
|
assigned_to: z.string().optional().describe('Assigned user ID (mapped to assignees array)'),
|
|
104
|
-
assignees: z
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
assignees: z
|
|
110
|
+
.array(z.string())
|
|
111
|
+
.optional()
|
|
112
|
+
.describe('Array of assigned user IDs (alternative to assigned_to for multiple assignees)'),
|
|
113
|
+
image_url: z
|
|
114
|
+
.string()
|
|
115
|
+
.max(2000)
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('Image URL (upload via create_upload_url with bucket "attachments", then set this to the public_url)'),
|
|
118
|
+
meter_reading: z
|
|
119
|
+
.number()
|
|
120
|
+
.min(0)
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('Meter/odometer reading at time of service'),
|
|
107
123
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
108
124
|
}, async (params) => {
|
|
109
125
|
try {
|
|
@@ -119,11 +135,22 @@ export function registerWriteTools(server, client) {
|
|
|
119
135
|
title: z.string().min(1).max(500).optional().describe('Work order title'),
|
|
120
136
|
description: z.string().optional().describe('Detailed description'),
|
|
121
137
|
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']).optional().describe('Priority level'),
|
|
122
|
-
status: z
|
|
138
|
+
status: z
|
|
139
|
+
.enum(['NEW', 'IN_PROGRESS', 'ON_HOLD', 'REJECTED', 'COMPLETED', 'CANCELLED'])
|
|
140
|
+
.optional()
|
|
141
|
+
.describe('Status'),
|
|
123
142
|
type: z.enum(['PM', 'REACTIVE']).optional().describe('Work order type'),
|
|
124
143
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
125
|
-
building_id: z
|
|
126
|
-
|
|
144
|
+
building_id: z
|
|
145
|
+
.string()
|
|
146
|
+
.uuid()
|
|
147
|
+
.optional()
|
|
148
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
149
|
+
location_id: z
|
|
150
|
+
.string()
|
|
151
|
+
.uuid()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
127
154
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
128
155
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
129
156
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
@@ -131,9 +158,20 @@ export function registerWriteTools(server, client) {
|
|
|
131
158
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
132
159
|
work_category_id: z.string().uuid().optional().describe('Work category ID'),
|
|
133
160
|
assigned_to: z.string().optional().describe('Assigned user ID (mapped to assignees array)'),
|
|
134
|
-
assignees: z
|
|
135
|
-
|
|
136
|
-
|
|
161
|
+
assignees: z
|
|
162
|
+
.array(z.string())
|
|
163
|
+
.optional()
|
|
164
|
+
.describe('Array of assigned user IDs (alternative to assigned_to for multiple assignees)'),
|
|
165
|
+
image_url: z
|
|
166
|
+
.string()
|
|
167
|
+
.max(2000)
|
|
168
|
+
.optional()
|
|
169
|
+
.describe('Image URL (upload via create_upload_url with bucket "attachments", then set this to the public_url)'),
|
|
170
|
+
meter_reading: z
|
|
171
|
+
.number()
|
|
172
|
+
.min(0)
|
|
173
|
+
.optional()
|
|
174
|
+
.describe('Meter/odometer reading at time of service'),
|
|
137
175
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
138
176
|
}, async ({ id, ...rest }) => {
|
|
139
177
|
try {
|
|
@@ -159,23 +197,54 @@ export function registerWriteTools(server, client) {
|
|
|
159
197
|
server.tool('create_asset', 'Create a new asset. Requires assets:write scope. IMPORTANT — Location hierarchy: always resolve top-down by calling list_sites first, then list_buildings filtered by site_id, then list_locations filtered by building_id. Provide all three IDs (site_id, building_id, location_id) explicitly. System hierarchy: similarly resolve via list_system_classes → list_system_groups → list_systems.', {
|
|
160
198
|
name: z.string().min(1).max(500).describe('Asset name (required)'),
|
|
161
199
|
description: z.string().optional().describe('Description'),
|
|
162
|
-
asset_id: z
|
|
200
|
+
asset_id: z
|
|
201
|
+
.string()
|
|
202
|
+
.max(100)
|
|
203
|
+
.optional()
|
|
204
|
+
.describe('Custom asset identifier (unique per tenant)'),
|
|
163
205
|
asset_type_id: z.string().uuid().optional().describe('Asset type ID (from asset_types)'),
|
|
164
|
-
manufacturer_id: z
|
|
206
|
+
manufacturer_id: z
|
|
207
|
+
.string()
|
|
208
|
+
.uuid()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe('Manufacturer ID (from manufacturers)'),
|
|
165
211
|
model: z.string().max(500).optional().describe('Model name/number'),
|
|
166
212
|
serial_number: z.string().max(200).optional().describe('Serial number'),
|
|
167
213
|
purchase_cost: z.number().min(0).optional().describe('Purchase cost'),
|
|
168
214
|
purchase_date: z.string().optional().describe('Purchase date (ISO 8601)'),
|
|
169
215
|
expected_lifetime_years: z.number().min(0).optional().describe('Expected lifetime in years'),
|
|
170
216
|
condition_score: z.number().min(0).max(100).optional().describe('Condition score (0-100)'),
|
|
171
|
-
risk_factor: z
|
|
217
|
+
risk_factor: z
|
|
218
|
+
.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'])
|
|
219
|
+
.optional()
|
|
220
|
+
.describe('Risk factor (CRITICAL, HIGH, MEDIUM, LOW)'),
|
|
172
221
|
status_id: z.string().max(100).optional().describe('Status identifier'),
|
|
173
222
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
174
|
-
building_id: z
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
223
|
+
building_id: z
|
|
224
|
+
.string()
|
|
225
|
+
.uuid()
|
|
226
|
+
.optional()
|
|
227
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
228
|
+
location_id: z
|
|
229
|
+
.string()
|
|
230
|
+
.uuid()
|
|
231
|
+
.optional()
|
|
232
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
233
|
+
system_class_id: z
|
|
234
|
+
.string()
|
|
235
|
+
.uuid()
|
|
236
|
+
.optional()
|
|
237
|
+
.describe('System class ID — resolve first via list_system_classes'),
|
|
238
|
+
system_group_id: z
|
|
239
|
+
.string()
|
|
240
|
+
.uuid()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe('System group ID — resolve second via list_system_groups filtered by system_class_id'),
|
|
243
|
+
system_id: z
|
|
244
|
+
.string()
|
|
245
|
+
.uuid()
|
|
246
|
+
.optional()
|
|
247
|
+
.describe('System ID — resolve last via list_systems filtered by system_group_id'),
|
|
179
248
|
image_url: z.string().max(2000).optional().describe('Image URL'),
|
|
180
249
|
last_maintenance_date: z.string().optional().describe('Last maintenance date (ISO 8601)'),
|
|
181
250
|
quantity: z.number().min(0).optional().describe('Quantity'),
|
|
@@ -183,15 +252,49 @@ export function registerWriteTools(server, client) {
|
|
|
183
252
|
unit_replacement_value: z.number().min(0).optional().describe('Unit replacement value'),
|
|
184
253
|
cost_per_sq_ft: z.number().min(0).optional().describe('Cost per square foot'),
|
|
185
254
|
salvage_value: z.number().min(0).optional().describe('Salvage value'),
|
|
186
|
-
salvage_value_percentage: z
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
255
|
+
salvage_value_percentage: z
|
|
256
|
+
.number()
|
|
257
|
+
.min(0)
|
|
258
|
+
.max(100)
|
|
259
|
+
.optional()
|
|
260
|
+
.describe('Salvage value percentage (0-100)'),
|
|
261
|
+
consequence_of_failure_score: z
|
|
262
|
+
.number()
|
|
263
|
+
.int()
|
|
264
|
+
.min(0)
|
|
265
|
+
.optional()
|
|
266
|
+
.describe('Consequence of failure score'),
|
|
267
|
+
likelihood_of_failure_score: z
|
|
268
|
+
.number()
|
|
269
|
+
.int()
|
|
270
|
+
.min(0)
|
|
271
|
+
.optional()
|
|
272
|
+
.describe('Likelihood of failure score'),
|
|
273
|
+
safety_impact: z
|
|
274
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
275
|
+
.optional()
|
|
276
|
+
.describe('Safety impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
277
|
+
service_impact: z
|
|
278
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
279
|
+
.optional()
|
|
280
|
+
.describe('Service impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
281
|
+
environmental_impact: z
|
|
282
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
283
|
+
.optional()
|
|
284
|
+
.describe('Environmental impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
285
|
+
regulatory_impact: z
|
|
286
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
287
|
+
.optional()
|
|
288
|
+
.describe('Regulatory impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
289
|
+
reputation_impact: z
|
|
290
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
291
|
+
.optional()
|
|
292
|
+
.describe('Reputation impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
293
|
+
current_meter_reading: z
|
|
294
|
+
.number()
|
|
295
|
+
.min(0)
|
|
296
|
+
.optional()
|
|
297
|
+
.describe('Current meter/odometer reading'),
|
|
195
298
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
196
299
|
}, async (params) => {
|
|
197
300
|
try {
|
|
@@ -206,40 +309,105 @@ export function registerWriteTools(server, client) {
|
|
|
206
309
|
id: z.string().uuid().describe('Asset ID'),
|
|
207
310
|
name: z.string().min(1).max(500).optional().describe('Asset name'),
|
|
208
311
|
description: z.string().optional().describe('Description'),
|
|
209
|
-
asset_id: z
|
|
312
|
+
asset_id: z
|
|
313
|
+
.string()
|
|
314
|
+
.max(100)
|
|
315
|
+
.optional()
|
|
316
|
+
.describe('Custom asset identifier (unique per tenant)'),
|
|
210
317
|
asset_type_id: z.string().uuid().optional().describe('Asset type ID (from asset_types)'),
|
|
211
|
-
manufacturer_id: z
|
|
318
|
+
manufacturer_id: z
|
|
319
|
+
.string()
|
|
320
|
+
.uuid()
|
|
321
|
+
.optional()
|
|
322
|
+
.describe('Manufacturer ID (from manufacturers)'),
|
|
212
323
|
model: z.string().max(500).optional().describe('Model name/number'),
|
|
213
324
|
serial_number: z.string().max(200).optional().describe('Serial number'),
|
|
214
325
|
purchase_cost: z.number().min(0).optional().describe('Purchase cost'),
|
|
215
326
|
purchase_date: z.string().optional().describe('Purchase date (ISO 8601)'),
|
|
216
327
|
expected_lifetime_years: z.number().min(0).optional().describe('Expected lifetime in years'),
|
|
217
328
|
condition_score: z.number().min(0).max(100).optional().describe('Condition score (0-100)'),
|
|
218
|
-
risk_factor: z
|
|
329
|
+
risk_factor: z
|
|
330
|
+
.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'])
|
|
331
|
+
.optional()
|
|
332
|
+
.describe('Risk factor (CRITICAL, HIGH, MEDIUM, LOW)'),
|
|
219
333
|
status_id: z.string().max(100).optional().describe('Status identifier'),
|
|
220
334
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
221
|
-
building_id: z
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
335
|
+
building_id: z
|
|
336
|
+
.string()
|
|
337
|
+
.uuid()
|
|
338
|
+
.optional()
|
|
339
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
340
|
+
location_id: z
|
|
341
|
+
.string()
|
|
342
|
+
.uuid()
|
|
343
|
+
.optional()
|
|
344
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
345
|
+
system_class_id: z
|
|
346
|
+
.string()
|
|
347
|
+
.uuid()
|
|
348
|
+
.optional()
|
|
349
|
+
.describe('System class ID — resolve first via list_system_classes'),
|
|
350
|
+
system_group_id: z
|
|
351
|
+
.string()
|
|
352
|
+
.uuid()
|
|
353
|
+
.optional()
|
|
354
|
+
.describe('System group ID — resolve second via list_system_groups filtered by system_class_id'),
|
|
355
|
+
system_id: z
|
|
356
|
+
.string()
|
|
357
|
+
.uuid()
|
|
358
|
+
.optional()
|
|
359
|
+
.describe('System ID — resolve last via list_systems filtered by system_group_id'),
|
|
226
360
|
image_url: z.string().max(2000).optional().describe('Image URL'),
|
|
227
361
|
last_maintenance_date: z.string().optional().describe('Last maintenance date (ISO 8601)'),
|
|
228
|
-
current_meter_reading: z
|
|
362
|
+
current_meter_reading: z
|
|
363
|
+
.number()
|
|
364
|
+
.min(0)
|
|
365
|
+
.optional()
|
|
366
|
+
.describe('Current meter/odometer reading'),
|
|
229
367
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
230
368
|
quantity: z.number().min(0).optional().describe('Quantity'),
|
|
231
369
|
unit_of_measure: z.string().max(100).optional().describe('Unit of measure'),
|
|
232
370
|
unit_replacement_value: z.number().min(0).optional().describe('Unit replacement value'),
|
|
233
371
|
cost_per_sq_ft: z.number().min(0).optional().describe('Cost per square foot'),
|
|
234
372
|
salvage_value: z.number().min(0).optional().describe('Salvage value'),
|
|
235
|
-
salvage_value_percentage: z
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
373
|
+
salvage_value_percentage: z
|
|
374
|
+
.number()
|
|
375
|
+
.min(0)
|
|
376
|
+
.max(100)
|
|
377
|
+
.optional()
|
|
378
|
+
.describe('Salvage value percentage (0-100)'),
|
|
379
|
+
consequence_of_failure_score: z
|
|
380
|
+
.number()
|
|
381
|
+
.int()
|
|
382
|
+
.min(0)
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Consequence of failure score'),
|
|
385
|
+
likelihood_of_failure_score: z
|
|
386
|
+
.number()
|
|
387
|
+
.int()
|
|
388
|
+
.min(0)
|
|
389
|
+
.optional()
|
|
390
|
+
.describe('Likelihood of failure score'),
|
|
391
|
+
safety_impact: z
|
|
392
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
393
|
+
.optional()
|
|
394
|
+
.describe('Safety impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
395
|
+
service_impact: z
|
|
396
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
397
|
+
.optional()
|
|
398
|
+
.describe('Service impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
399
|
+
environmental_impact: z
|
|
400
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
401
|
+
.optional()
|
|
402
|
+
.describe('Environmental impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
403
|
+
regulatory_impact: z
|
|
404
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
405
|
+
.optional()
|
|
406
|
+
.describe('Regulatory impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
407
|
+
reputation_impact: z
|
|
408
|
+
.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
|
|
409
|
+
.optional()
|
|
410
|
+
.describe('Reputation impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
|
|
243
411
|
}, async ({ id, ...rest }) => {
|
|
244
412
|
try {
|
|
245
413
|
const result = await client.update('assets', id, buildBody(rest));
|
|
@@ -265,10 +433,21 @@ export function registerWriteTools(server, client) {
|
|
|
265
433
|
title: z.string().min(1).max(500).describe('Work request title (required)'),
|
|
266
434
|
description: z.string().optional().describe('Description'),
|
|
267
435
|
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Priority level'),
|
|
268
|
-
status: z
|
|
436
|
+
status: z
|
|
437
|
+
.enum(['SUBMITTED', 'APPROVED', 'REJECTED', 'CONVERTED'])
|
|
438
|
+
.optional()
|
|
439
|
+
.describe('Status'),
|
|
269
440
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
270
|
-
building_id: z
|
|
271
|
-
|
|
441
|
+
building_id: z
|
|
442
|
+
.string()
|
|
443
|
+
.uuid()
|
|
444
|
+
.optional()
|
|
445
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
446
|
+
location_id: z
|
|
447
|
+
.string()
|
|
448
|
+
.uuid()
|
|
449
|
+
.optional()
|
|
450
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
272
451
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
273
452
|
system_id: z.string().uuid().optional().describe('System ID'),
|
|
274
453
|
work_category_id: z.string().uuid().optional().describe('Work category ID'),
|
|
@@ -286,10 +465,21 @@ export function registerWriteTools(server, client) {
|
|
|
286
465
|
title: z.string().min(1).max(500).optional().describe('Work request title'),
|
|
287
466
|
description: z.string().optional().describe('Description'),
|
|
288
467
|
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Priority level'),
|
|
289
|
-
status: z
|
|
468
|
+
status: z
|
|
469
|
+
.enum(['SUBMITTED', 'APPROVED', 'REJECTED', 'CONVERTED'])
|
|
470
|
+
.optional()
|
|
471
|
+
.describe('Status'),
|
|
290
472
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
291
|
-
building_id: z
|
|
292
|
-
|
|
473
|
+
building_id: z
|
|
474
|
+
.string()
|
|
475
|
+
.uuid()
|
|
476
|
+
.optional()
|
|
477
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
478
|
+
location_id: z
|
|
479
|
+
.string()
|
|
480
|
+
.uuid()
|
|
481
|
+
.optional()
|
|
482
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
293
483
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
294
484
|
system_id: z.string().uuid().optional().describe('System ID'),
|
|
295
485
|
work_category_id: z.string().uuid().optional().describe('Work category ID'),
|
|
@@ -324,8 +514,15 @@ export function registerWriteTools(server, client) {
|
|
|
324
514
|
state: z.string().max(100).optional().describe('State/province'),
|
|
325
515
|
country: z.string().max(100).optional().describe('Country'),
|
|
326
516
|
status: z.string().max(50).optional().describe('Vendor status'),
|
|
327
|
-
categories: z
|
|
328
|
-
|
|
517
|
+
categories: z
|
|
518
|
+
.array(z.string().max(100))
|
|
519
|
+
.optional()
|
|
520
|
+
.describe('Vendor categories (e.g. ["HVAC", "Plumbing"])'),
|
|
521
|
+
website: z
|
|
522
|
+
.string()
|
|
523
|
+
.max(500)
|
|
524
|
+
.optional()
|
|
525
|
+
.describe('Website URL (protocol and www prefix are stripped automatically)'),
|
|
329
526
|
description: z.string().optional().describe('Description'),
|
|
330
527
|
}, async (params) => {
|
|
331
528
|
try {
|
|
@@ -347,8 +544,15 @@ export function registerWriteTools(server, client) {
|
|
|
347
544
|
state: z.string().max(100).optional().describe('State/province'),
|
|
348
545
|
country: z.string().max(100).optional().describe('Country'),
|
|
349
546
|
status: z.string().max(50).optional().describe('Vendor status'),
|
|
350
|
-
categories: z
|
|
351
|
-
|
|
547
|
+
categories: z
|
|
548
|
+
.array(z.string().max(100))
|
|
549
|
+
.optional()
|
|
550
|
+
.describe('Vendor categories (e.g. ["HVAC", "Plumbing"])'),
|
|
551
|
+
website: z
|
|
552
|
+
.string()
|
|
553
|
+
.max(500)
|
|
554
|
+
.optional()
|
|
555
|
+
.describe('Website URL (protocol and www prefix are stripped automatically)'),
|
|
352
556
|
description: z.string().optional().describe('Description'),
|
|
353
557
|
}, async ({ id, ...rest }) => {
|
|
354
558
|
try {
|
|
@@ -440,8 +644,18 @@ export function registerWriteTools(server, client) {
|
|
|
440
644
|
floors: z.number().int().optional().describe('Number of floors'),
|
|
441
645
|
area_sqft: z.number().min(0).optional().describe('Area in square feet'),
|
|
442
646
|
type: z.string().max(100).optional().describe('Building type label'),
|
|
443
|
-
building_type_id: z
|
|
444
|
-
|
|
647
|
+
building_type_id: z
|
|
648
|
+
.string()
|
|
649
|
+
.uuid()
|
|
650
|
+
.optional()
|
|
651
|
+
.describe('Building type ID (from building_types)'),
|
|
652
|
+
year_built: z
|
|
653
|
+
.number()
|
|
654
|
+
.int()
|
|
655
|
+
.min(1800)
|
|
656
|
+
.max(2100)
|
|
657
|
+
.optional()
|
|
658
|
+
.describe('Year the building was constructed'),
|
|
445
659
|
}, async (params) => {
|
|
446
660
|
try {
|
|
447
661
|
const result = await client.create('buildings', buildBody(params));
|
|
@@ -458,8 +672,18 @@ export function registerWriteTools(server, client) {
|
|
|
458
672
|
floors: z.number().int().optional().describe('Number of floors'),
|
|
459
673
|
area_sqft: z.number().min(0).optional().describe('Area in square feet'),
|
|
460
674
|
type: z.string().max(100).optional().describe('Building type label'),
|
|
461
|
-
building_type_id: z
|
|
462
|
-
|
|
675
|
+
building_type_id: z
|
|
676
|
+
.string()
|
|
677
|
+
.uuid()
|
|
678
|
+
.optional()
|
|
679
|
+
.describe('Building type ID (from building_types)'),
|
|
680
|
+
year_built: z
|
|
681
|
+
.number()
|
|
682
|
+
.int()
|
|
683
|
+
.min(1800)
|
|
684
|
+
.max(2100)
|
|
685
|
+
.optional()
|
|
686
|
+
.describe('Year the building was constructed'),
|
|
463
687
|
}, async ({ id, ...rest }) => {
|
|
464
688
|
try {
|
|
465
689
|
const result = await client.update('buildings', id, buildBody(rest));
|
|
@@ -487,7 +711,11 @@ export function registerWriteTools(server, client) {
|
|
|
487
711
|
floor: z.string().max(50).optional().describe('Floor identifier'),
|
|
488
712
|
area: z.number().min(0).optional().describe('Area (sq ft or sq m)'),
|
|
489
713
|
type: z.string().max(100).optional().describe('Location type label'),
|
|
490
|
-
location_type_id: z
|
|
714
|
+
location_type_id: z
|
|
715
|
+
.string()
|
|
716
|
+
.uuid()
|
|
717
|
+
.optional()
|
|
718
|
+
.describe('Location type ID (from location_types)'),
|
|
491
719
|
}, async (params) => {
|
|
492
720
|
try {
|
|
493
721
|
const result = await client.create('locations', buildBody(params));
|
|
@@ -504,7 +732,11 @@ export function registerWriteTools(server, client) {
|
|
|
504
732
|
floor: z.string().max(50).optional().describe('Floor identifier'),
|
|
505
733
|
area: z.number().min(0).optional().describe('Area (sq ft or sq m)'),
|
|
506
734
|
type: z.string().max(100).optional().describe('Location type label'),
|
|
507
|
-
location_type_id: z
|
|
735
|
+
location_type_id: z
|
|
736
|
+
.string()
|
|
737
|
+
.uuid()
|
|
738
|
+
.optional()
|
|
739
|
+
.describe('Location type ID (from location_types)'),
|
|
508
740
|
}, async ({ id, ...rest }) => {
|
|
509
741
|
try {
|
|
510
742
|
const result = await client.update('locations', id, buildBody(rest));
|
|
@@ -529,14 +761,39 @@ export function registerWriteTools(server, client) {
|
|
|
529
761
|
server.tool('create_pm_schedule', 'Create a new preventive maintenance schedule. Requires pm_schedules:write scope. IMPORTANT — Location hierarchy: always resolve top-down by calling list_sites first, then list_buildings filtered by site_id, then list_locations filtered by building_id. Provide all three IDs explicitly.', {
|
|
530
762
|
title: z.string().min(1).max(500).describe('PM schedule title (required)'),
|
|
531
763
|
description: z.string().optional().describe('Description'),
|
|
532
|
-
frequency: z
|
|
533
|
-
|
|
764
|
+
frequency: z
|
|
765
|
+
.enum([
|
|
766
|
+
'DAILY',
|
|
767
|
+
'WEEKLY',
|
|
768
|
+
'MONTHLY',
|
|
769
|
+
'QUARTERLY',
|
|
770
|
+
'SEMI_ANNUAL',
|
|
771
|
+
'ANNUAL',
|
|
772
|
+
'FIVE_YEARLY',
|
|
773
|
+
'CUSTOM',
|
|
774
|
+
])
|
|
775
|
+
.optional()
|
|
776
|
+
.describe('Frequency'),
|
|
777
|
+
custom_interval_weeks: z
|
|
778
|
+
.number()
|
|
779
|
+
.int()
|
|
780
|
+
.min(1)
|
|
781
|
+
.optional()
|
|
782
|
+
.describe('Custom interval in weeks (when frequency is CUSTOM)'),
|
|
534
783
|
next_due: z.string().optional().describe('Next due date (ISO 8601)'),
|
|
535
784
|
estimated_hours: z.number().min(0).optional().describe('Estimated hours'),
|
|
536
785
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
537
786
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
538
|
-
building_id: z
|
|
539
|
-
|
|
787
|
+
building_id: z
|
|
788
|
+
.string()
|
|
789
|
+
.uuid()
|
|
790
|
+
.optional()
|
|
791
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
792
|
+
location_id: z
|
|
793
|
+
.string()
|
|
794
|
+
.uuid()
|
|
795
|
+
.optional()
|
|
796
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
540
797
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
541
798
|
work_category: z.string().max(200).optional().describe('Work category label'),
|
|
542
799
|
schedule_type: z.string().max(100).optional().describe('Schedule type'),
|
|
@@ -546,18 +803,28 @@ export function registerWriteTools(server, client) {
|
|
|
546
803
|
status: z.enum(['active', 'inactive']).optional().describe('Schedule status'),
|
|
547
804
|
auto_generate_wo: z.boolean().optional().describe('Auto-generate work orders'),
|
|
548
805
|
floating: z.boolean().optional().describe('Floating schedule (due date based on completion)'),
|
|
549
|
-
meter_based: z
|
|
550
|
-
|
|
806
|
+
meter_based: z
|
|
807
|
+
.boolean()
|
|
808
|
+
.optional()
|
|
809
|
+
.describe('Whether this PM triggers at meter intervals (e.g. every 5000 km)'),
|
|
810
|
+
meter_interval: z
|
|
811
|
+
.number()
|
|
812
|
+
.min(0)
|
|
813
|
+
.optional()
|
|
814
|
+
.describe('Meter interval — trigger every N units'),
|
|
551
815
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
552
816
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
553
817
|
asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
|
|
554
818
|
system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
|
|
555
819
|
location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
|
|
556
|
-
tasks: z
|
|
820
|
+
tasks: z
|
|
821
|
+
.array(z.object({
|
|
557
822
|
id: z.string().describe('Unique task ID (use a random string)'),
|
|
558
823
|
description: z.string().describe('Task description'),
|
|
559
824
|
completed: z.boolean().describe('Whether the task is completed'),
|
|
560
|
-
}))
|
|
825
|
+
}))
|
|
826
|
+
.optional()
|
|
827
|
+
.describe('Checklist of tasks for this PM schedule'),
|
|
561
828
|
}, async (params) => {
|
|
562
829
|
try {
|
|
563
830
|
const result = await client.create('pm-schedules', buildBody(params));
|
|
@@ -571,14 +838,39 @@ export function registerWriteTools(server, client) {
|
|
|
571
838
|
id: z.string().uuid().describe('PM schedule ID'),
|
|
572
839
|
title: z.string().min(1).max(500).optional().describe('PM schedule title'),
|
|
573
840
|
description: z.string().optional().describe('Description'),
|
|
574
|
-
frequency: z
|
|
575
|
-
|
|
841
|
+
frequency: z
|
|
842
|
+
.enum([
|
|
843
|
+
'DAILY',
|
|
844
|
+
'WEEKLY',
|
|
845
|
+
'MONTHLY',
|
|
846
|
+
'QUARTERLY',
|
|
847
|
+
'SEMI_ANNUAL',
|
|
848
|
+
'ANNUAL',
|
|
849
|
+
'FIVE_YEARLY',
|
|
850
|
+
'CUSTOM',
|
|
851
|
+
])
|
|
852
|
+
.optional()
|
|
853
|
+
.describe('Frequency'),
|
|
854
|
+
custom_interval_weeks: z
|
|
855
|
+
.number()
|
|
856
|
+
.int()
|
|
857
|
+
.min(1)
|
|
858
|
+
.optional()
|
|
859
|
+
.describe('Custom interval in weeks (when frequency is CUSTOM)'),
|
|
576
860
|
next_due: z.string().optional().describe('Next due date (ISO 8601)'),
|
|
577
861
|
estimated_hours: z.number().min(0).optional().describe('Estimated hours'),
|
|
578
862
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
579
863
|
site_id: z.string().uuid().optional().describe('Site ID — resolve first via list_sites'),
|
|
580
|
-
building_id: z
|
|
581
|
-
|
|
864
|
+
building_id: z
|
|
865
|
+
.string()
|
|
866
|
+
.uuid()
|
|
867
|
+
.optional()
|
|
868
|
+
.describe('Building ID — resolve second via list_buildings filtered by site_id'),
|
|
869
|
+
location_id: z
|
|
870
|
+
.string()
|
|
871
|
+
.uuid()
|
|
872
|
+
.optional()
|
|
873
|
+
.describe('Location ID — resolve last via list_locations filtered by building_id'),
|
|
582
874
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
583
875
|
work_category: z.string().max(200).optional().describe('Work category label'),
|
|
584
876
|
schedule_type: z.string().max(100).optional().describe('Schedule type'),
|
|
@@ -589,17 +881,24 @@ export function registerWriteTools(server, client) {
|
|
|
589
881
|
auto_generate_wo: z.boolean().optional().describe('Auto-generate work orders'),
|
|
590
882
|
floating: z.boolean().optional().describe('Floating schedule (due date based on completion)'),
|
|
591
883
|
meter_based: z.boolean().optional().describe('Whether this PM triggers at meter intervals'),
|
|
592
|
-
meter_interval: z
|
|
884
|
+
meter_interval: z
|
|
885
|
+
.number()
|
|
886
|
+
.min(0)
|
|
887
|
+
.optional()
|
|
888
|
+
.describe('Meter interval — trigger every N units'),
|
|
593
889
|
meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
|
|
594
890
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
595
891
|
asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
|
|
596
892
|
system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
|
|
597
893
|
location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
|
|
598
|
-
tasks: z
|
|
894
|
+
tasks: z
|
|
895
|
+
.array(z.object({
|
|
599
896
|
id: z.string().describe('Unique task ID'),
|
|
600
897
|
description: z.string().describe('Task description'),
|
|
601
898
|
completed: z.boolean().describe('Whether the task is completed'),
|
|
602
|
-
}))
|
|
899
|
+
}))
|
|
900
|
+
.optional()
|
|
901
|
+
.describe('Checklist of tasks for this PM schedule'),
|
|
603
902
|
}, async ({ id, ...rest }) => {
|
|
604
903
|
try {
|
|
605
904
|
const result = await client.update('pm-schedules', id, buildBody(rest));
|
|
@@ -624,27 +923,66 @@ export function registerWriteTools(server, client) {
|
|
|
624
923
|
server.tool('create_pm_template', 'Create a new PM template. Templates are reusable PM definitions (not linked to a site/asset) that can seed new PM schedules. Requires pm_templates:write scope.', {
|
|
625
924
|
title: z.string().min(1).max(500).describe('PM template title (required, unique per tenant)'),
|
|
626
925
|
description: z.string().optional().describe('Description'),
|
|
627
|
-
frequency: z
|
|
628
|
-
|
|
926
|
+
frequency: z
|
|
927
|
+
.enum([
|
|
928
|
+
'DAILY',
|
|
929
|
+
'WEEKLY',
|
|
930
|
+
'MONTHLY',
|
|
931
|
+
'QUARTERLY',
|
|
932
|
+
'SEMI_ANNUAL',
|
|
933
|
+
'ANNUAL',
|
|
934
|
+
'FIVE_YEARLY',
|
|
935
|
+
'CUSTOM',
|
|
936
|
+
])
|
|
937
|
+
.optional()
|
|
938
|
+
.describe('Suggested maintenance frequency'),
|
|
939
|
+
custom_interval_weeks: z
|
|
940
|
+
.number()
|
|
941
|
+
.int()
|
|
942
|
+
.min(1)
|
|
943
|
+
.optional()
|
|
944
|
+
.describe('Custom interval in weeks (when frequency is CUSTOM)'),
|
|
629
945
|
work_category: z.string().max(200).optional().describe('Work category label (free text)'),
|
|
630
|
-
work_category_id: z
|
|
946
|
+
work_category_id: z
|
|
947
|
+
.string()
|
|
948
|
+
.uuid()
|
|
949
|
+
.optional()
|
|
950
|
+
.describe('Work category ID — resolve via list_work_categories'),
|
|
631
951
|
estimated_hours: z.number().min(0).optional().describe('Estimated hours'),
|
|
632
952
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
633
953
|
safety_requirements: z.string().optional().describe('Safety requirements'),
|
|
634
|
-
tasks: z
|
|
954
|
+
tasks: z
|
|
955
|
+
.array(z.object({
|
|
635
956
|
id: z.string().optional().describe('Unique task ID (auto-generated if omitted)'),
|
|
636
957
|
description: z.string().describe('Task description'),
|
|
637
|
-
completed: z
|
|
638
|
-
|
|
639
|
-
|
|
958
|
+
completed: z
|
|
959
|
+
.boolean()
|
|
960
|
+
.optional()
|
|
961
|
+
.describe('Whether the task is completed (defaults to false)'),
|
|
962
|
+
}))
|
|
963
|
+
.optional()
|
|
964
|
+
.describe('Checklist of tasks baked into this template'),
|
|
965
|
+
resources: z
|
|
966
|
+
.array(z.object({
|
|
640
967
|
name: z.string().optional().describe('Resource name'),
|
|
641
|
-
type: z
|
|
968
|
+
type: z
|
|
969
|
+
.enum(PM_RESOURCE_TYPES)
|
|
970
|
+
.optional()
|
|
971
|
+
.describe('Resource type (TOOL, PART, MATERIAL, or EQUIPMENT)'),
|
|
642
972
|
quantity: z.number().optional().describe('Quantity required'),
|
|
643
973
|
cost: z.number().optional().describe('Unit cost'),
|
|
644
|
-
}))
|
|
974
|
+
}))
|
|
975
|
+
.optional()
|
|
976
|
+
.describe('Resource references (parts, tools, materials, equipment)'),
|
|
645
977
|
documents: z.array(z.record(z.unknown())).optional().describe('Document references'),
|
|
646
|
-
asset_ids: z
|
|
647
|
-
|
|
978
|
+
asset_ids: z
|
|
979
|
+
.array(z.string().uuid())
|
|
980
|
+
.optional()
|
|
981
|
+
.describe('Default asset IDs to seed on derived schedules'),
|
|
982
|
+
location_ids: z
|
|
983
|
+
.array(z.string().uuid())
|
|
984
|
+
.optional()
|
|
985
|
+
.describe('Default location IDs to seed on derived schedules'),
|
|
648
986
|
}, async (params) => {
|
|
649
987
|
try {
|
|
650
988
|
const result = await client.create('pm-templates', buildBody(normalizePmTemplateBody(params)));
|
|
@@ -658,24 +996,53 @@ export function registerWriteTools(server, client) {
|
|
|
658
996
|
id: z.string().uuid().describe('PM template ID'),
|
|
659
997
|
title: z.string().min(1).max(500).optional().describe('PM template title'),
|
|
660
998
|
description: z.string().optional().describe('Description'),
|
|
661
|
-
frequency: z
|
|
662
|
-
|
|
999
|
+
frequency: z
|
|
1000
|
+
.enum([
|
|
1001
|
+
'DAILY',
|
|
1002
|
+
'WEEKLY',
|
|
1003
|
+
'MONTHLY',
|
|
1004
|
+
'QUARTERLY',
|
|
1005
|
+
'SEMI_ANNUAL',
|
|
1006
|
+
'ANNUAL',
|
|
1007
|
+
'FIVE_YEARLY',
|
|
1008
|
+
'CUSTOM',
|
|
1009
|
+
])
|
|
1010
|
+
.optional()
|
|
1011
|
+
.describe('Suggested maintenance frequency'),
|
|
1012
|
+
custom_interval_weeks: z
|
|
1013
|
+
.number()
|
|
1014
|
+
.int()
|
|
1015
|
+
.min(1)
|
|
1016
|
+
.optional()
|
|
1017
|
+
.describe('Custom interval in weeks (when frequency is CUSTOM)'),
|
|
663
1018
|
work_category: z.string().max(200).optional().describe('Work category label (free text)'),
|
|
664
1019
|
work_category_id: z.string().uuid().optional().describe('Work category ID'),
|
|
665
1020
|
estimated_hours: z.number().min(0).optional().describe('Estimated hours'),
|
|
666
1021
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
667
1022
|
safety_requirements: z.string().optional().describe('Safety requirements'),
|
|
668
|
-
tasks: z
|
|
1023
|
+
tasks: z
|
|
1024
|
+
.array(z.object({
|
|
669
1025
|
id: z.string().optional().describe('Unique task ID (auto-generated if omitted)'),
|
|
670
1026
|
description: z.string().describe('Task description'),
|
|
671
|
-
completed: z
|
|
672
|
-
|
|
673
|
-
|
|
1027
|
+
completed: z
|
|
1028
|
+
.boolean()
|
|
1029
|
+
.optional()
|
|
1030
|
+
.describe('Whether the task is completed (defaults to false)'),
|
|
1031
|
+
}))
|
|
1032
|
+
.optional()
|
|
1033
|
+
.describe('Checklist of tasks baked into this template'),
|
|
1034
|
+
resources: z
|
|
1035
|
+
.array(z.object({
|
|
674
1036
|
name: z.string().optional().describe('Resource name'),
|
|
675
|
-
type: z
|
|
1037
|
+
type: z
|
|
1038
|
+
.enum(PM_RESOURCE_TYPES)
|
|
1039
|
+
.optional()
|
|
1040
|
+
.describe('Resource type (TOOL, PART, MATERIAL, or EQUIPMENT)'),
|
|
676
1041
|
quantity: z.number().optional().describe('Quantity required'),
|
|
677
1042
|
cost: z.number().optional().describe('Unit cost'),
|
|
678
|
-
}))
|
|
1043
|
+
}))
|
|
1044
|
+
.optional()
|
|
1045
|
+
.describe('Resource references (parts, tools, materials, equipment)'),
|
|
679
1046
|
documents: z.array(z.record(z.unknown())).optional().describe('Document references'),
|
|
680
1047
|
asset_ids: z.array(z.string().uuid()).optional().describe('Default asset IDs'),
|
|
681
1048
|
location_ids: z.array(z.string().uuid()).optional().describe('Default location IDs'),
|
|
@@ -705,16 +1072,42 @@ export function registerWriteTools(server, client) {
|
|
|
705
1072
|
status: z.string().max(100).describe('Project status (required)'),
|
|
706
1073
|
start_date: z.string().describe('Start date (ISO 8601, required)'),
|
|
707
1074
|
project_code: z.string().max(100).optional().describe('Project code'),
|
|
708
|
-
project_type: z
|
|
1075
|
+
project_type: z
|
|
1076
|
+
.enum([
|
|
1077
|
+
'capital',
|
|
1078
|
+
'maintenance',
|
|
1079
|
+
'repair',
|
|
1080
|
+
'upgrade',
|
|
1081
|
+
'new_construction',
|
|
1082
|
+
'renovation',
|
|
1083
|
+
'deferred_maintenance',
|
|
1084
|
+
'other',
|
|
1085
|
+
])
|
|
1086
|
+
.optional()
|
|
1087
|
+
.describe('Project type'),
|
|
709
1088
|
current_phase: z.string().max(100).optional().describe('Current phase'),
|
|
710
1089
|
description: z.string().optional().describe('Description'),
|
|
711
1090
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
712
1091
|
budget: z.number().min(0).optional().describe('Total budget'),
|
|
713
1092
|
project_manager: z.string().max(200).optional().describe('Project manager name'),
|
|
714
|
-
progress_percentage: z
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1093
|
+
progress_percentage: z
|
|
1094
|
+
.number()
|
|
1095
|
+
.min(0)
|
|
1096
|
+
.max(100)
|
|
1097
|
+
.optional()
|
|
1098
|
+
.describe('Progress percentage (0-100)'),
|
|
1099
|
+
health_status: z
|
|
1100
|
+
.enum(['on_track', 'at_risk', 'delayed', 'critical'])
|
|
1101
|
+
.optional()
|
|
1102
|
+
.describe('Health status'),
|
|
1103
|
+
budget_status: z
|
|
1104
|
+
.enum(['off_track', 'on_track', 'not_set', 'monitor'])
|
|
1105
|
+
.optional()
|
|
1106
|
+
.describe('Budget status'),
|
|
1107
|
+
progress_status: z
|
|
1108
|
+
.enum(['off_track', 'on_track', 'monitor'])
|
|
1109
|
+
.optional()
|
|
1110
|
+
.describe('Progress status'),
|
|
718
1111
|
image_url: z.string().max(2000).optional().describe('Image URL'),
|
|
719
1112
|
}, async (params) => {
|
|
720
1113
|
try {
|
|
@@ -731,16 +1124,42 @@ export function registerWriteTools(server, client) {
|
|
|
731
1124
|
status: z.string().max(100).optional().describe('Project status'),
|
|
732
1125
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
733
1126
|
project_code: z.string().max(100).optional().describe('Project code'),
|
|
734
|
-
project_type: z
|
|
1127
|
+
project_type: z
|
|
1128
|
+
.enum([
|
|
1129
|
+
'capital',
|
|
1130
|
+
'maintenance',
|
|
1131
|
+
'repair',
|
|
1132
|
+
'upgrade',
|
|
1133
|
+
'new_construction',
|
|
1134
|
+
'renovation',
|
|
1135
|
+
'deferred_maintenance',
|
|
1136
|
+
'other',
|
|
1137
|
+
])
|
|
1138
|
+
.optional()
|
|
1139
|
+
.describe('Project type'),
|
|
735
1140
|
current_phase: z.string().max(100).optional().describe('Current phase'),
|
|
736
1141
|
description: z.string().optional().describe('Description'),
|
|
737
1142
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
738
1143
|
budget: z.number().min(0).optional().describe('Total budget'),
|
|
739
1144
|
project_manager: z.string().max(200).optional().describe('Project manager name'),
|
|
740
|
-
progress_percentage: z
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1145
|
+
progress_percentage: z
|
|
1146
|
+
.number()
|
|
1147
|
+
.min(0)
|
|
1148
|
+
.max(100)
|
|
1149
|
+
.optional()
|
|
1150
|
+
.describe('Progress percentage (0-100)'),
|
|
1151
|
+
health_status: z
|
|
1152
|
+
.enum(['on_track', 'at_risk', 'delayed', 'critical'])
|
|
1153
|
+
.optional()
|
|
1154
|
+
.describe('Health status'),
|
|
1155
|
+
budget_status: z
|
|
1156
|
+
.enum(['off_track', 'on_track', 'not_set', 'monitor'])
|
|
1157
|
+
.optional()
|
|
1158
|
+
.describe('Budget status'),
|
|
1159
|
+
progress_status: z
|
|
1160
|
+
.enum(['off_track', 'on_track', 'monitor'])
|
|
1161
|
+
.optional()
|
|
1162
|
+
.describe('Progress status'),
|
|
744
1163
|
image_url: z.string().max(2000).optional().describe('Image URL'),
|
|
745
1164
|
}, async ({ id, ...rest }) => {
|
|
746
1165
|
try {
|
|
@@ -824,7 +1243,10 @@ export function registerWriteTools(server, client) {
|
|
|
824
1243
|
tax_amount: z.number().min(0).optional().describe('Tax amount'),
|
|
825
1244
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
826
1245
|
paid_date: z.string().optional().describe('Paid date (ISO 8601)'),
|
|
827
|
-
status: z
|
|
1246
|
+
status: z
|
|
1247
|
+
.enum(['pending', 'approved', 'paid', 'voided'])
|
|
1248
|
+
.optional()
|
|
1249
|
+
.describe('Invoice status'),
|
|
828
1250
|
notes: z.string().optional().describe('Notes'),
|
|
829
1251
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
830
1252
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
@@ -849,7 +1271,10 @@ export function registerWriteTools(server, client) {
|
|
|
849
1271
|
tax_amount: z.number().min(0).optional().describe('Tax amount'),
|
|
850
1272
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
851
1273
|
paid_date: z.string().optional().describe('Paid date (ISO 8601)'),
|
|
852
|
-
status: z
|
|
1274
|
+
status: z
|
|
1275
|
+
.enum(['pending', 'approved', 'paid', 'voided'])
|
|
1276
|
+
.optional()
|
|
1277
|
+
.describe('Invoice status'),
|
|
853
1278
|
notes: z.string().optional().describe('Notes'),
|
|
854
1279
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
855
1280
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
@@ -881,7 +1306,10 @@ export function registerWriteTools(server, client) {
|
|
|
881
1306
|
po_number: z.string().min(1).max(200).describe('PO number (required)'),
|
|
882
1307
|
amount: z.number().min(0).describe('PO amount (required)'),
|
|
883
1308
|
description: z.string().optional().describe('Description'),
|
|
884
|
-
status: z
|
|
1309
|
+
status: z
|
|
1310
|
+
.enum(['draft', 'issued', 'partially_received', 'received', 'closed', 'cancelled'])
|
|
1311
|
+
.optional()
|
|
1312
|
+
.describe('PO status'),
|
|
885
1313
|
issued_date: z.string().optional().describe('Issued date (ISO 8601)'),
|
|
886
1314
|
expected_date: z.string().optional().describe('Expected delivery date (ISO 8601)'),
|
|
887
1315
|
notes: z.string().optional().describe('Notes'),
|
|
@@ -903,7 +1331,10 @@ export function registerWriteTools(server, client) {
|
|
|
903
1331
|
po_number: z.string().min(1).max(200).optional().describe('PO number'),
|
|
904
1332
|
amount: z.number().min(0).optional().describe('PO amount'),
|
|
905
1333
|
description: z.string().optional().describe('Description'),
|
|
906
|
-
status: z
|
|
1334
|
+
status: z
|
|
1335
|
+
.enum(['draft', 'issued', 'partially_received', 'received', 'closed', 'cancelled'])
|
|
1336
|
+
.optional()
|
|
1337
|
+
.describe('PO status'),
|
|
907
1338
|
issued_date: z.string().optional().describe('Issued date (ISO 8601)'),
|
|
908
1339
|
expected_date: z.string().optional().describe('Expected delivery date (ISO 8601)'),
|
|
909
1340
|
notes: z.string().optional().describe('Notes'),
|
|
@@ -1037,7 +1468,10 @@ export function registerWriteTools(server, client) {
|
|
|
1037
1468
|
server.tool('create_project_document_folder_template', 'Create a project document folder template. Requires project_document_folder_templates:write scope.', {
|
|
1038
1469
|
name: z.string().min(1).max(500).describe('Template name (required)'),
|
|
1039
1470
|
description: z.string().max(2000).optional().describe('Template description'),
|
|
1040
|
-
structure: z
|
|
1471
|
+
structure: z
|
|
1472
|
+
.array(z.record(z.string(), z.unknown()))
|
|
1473
|
+
.optional()
|
|
1474
|
+
.describe('Folder hierarchy as JSON array'),
|
|
1041
1475
|
is_default: z.boolean().optional().describe('Whether this is the default template'),
|
|
1042
1476
|
}, async (params) => {
|
|
1043
1477
|
try {
|
|
@@ -1052,7 +1486,10 @@ export function registerWriteTools(server, client) {
|
|
|
1052
1486
|
id: z.string().uuid().describe('Template ID'),
|
|
1053
1487
|
name: z.string().min(1).max(500).optional().describe('Template name'),
|
|
1054
1488
|
description: z.string().max(2000).optional().describe('Template description'),
|
|
1055
|
-
structure: z
|
|
1489
|
+
structure: z
|
|
1490
|
+
.array(z.record(z.string(), z.unknown()))
|
|
1491
|
+
.optional()
|
|
1492
|
+
.describe('Folder hierarchy as JSON array'),
|
|
1056
1493
|
is_default: z.boolean().optional().describe('Whether this is the default template'),
|
|
1057
1494
|
}, async ({ id, ...rest }) => {
|
|
1058
1495
|
try {
|
|
@@ -1125,7 +1562,6 @@ export function registerWriteTools(server, client) {
|
|
|
1125
1562
|
server.tool('create_asset_comment', 'Create a new comment on an asset. Requires asset_comments:write scope.', {
|
|
1126
1563
|
asset_id: z.string().uuid().describe('Asset ID (required)'),
|
|
1127
1564
|
comment: z.string().min(1).describe('Comment text (required)'),
|
|
1128
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1129
1565
|
}, async (params) => {
|
|
1130
1566
|
try {
|
|
1131
1567
|
const result = await client.create('asset-comments', buildBody(params));
|
|
@@ -1138,7 +1574,6 @@ export function registerWriteTools(server, client) {
|
|
|
1138
1574
|
server.tool('update_asset_comment', 'Update an existing asset comment by ID. Requires asset_comments:write scope.', {
|
|
1139
1575
|
id: z.string().uuid().describe('Asset comment ID'),
|
|
1140
1576
|
comment: z.string().min(1).optional().describe('Comment text'),
|
|
1141
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1142
1577
|
}, async ({ id, ...rest }) => {
|
|
1143
1578
|
try {
|
|
1144
1579
|
const result = await client.update('asset-comments', id, buildBody(rest));
|
|
@@ -1163,7 +1598,6 @@ export function registerWriteTools(server, client) {
|
|
|
1163
1598
|
server.tool('create_work_order_comment', 'Create a new comment on a work order. Requires work_order_comments:write scope.', {
|
|
1164
1599
|
work_order_id: z.string().uuid().describe('Work order ID (required)'),
|
|
1165
1600
|
comment: z.string().min(1).describe('Comment text (required)'),
|
|
1166
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1167
1601
|
}, async (params) => {
|
|
1168
1602
|
try {
|
|
1169
1603
|
const result = await client.create('work-order-comments', buildBody(params));
|
|
@@ -1176,7 +1610,6 @@ export function registerWriteTools(server, client) {
|
|
|
1176
1610
|
server.tool('update_work_order_comment', 'Update an existing work order comment by ID. Requires work_order_comments:write scope.', {
|
|
1177
1611
|
id: z.string().uuid().describe('Work order comment ID'),
|
|
1178
1612
|
comment: z.string().min(1).optional().describe('Comment text'),
|
|
1179
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1180
1613
|
}, async ({ id, ...rest }) => {
|
|
1181
1614
|
try {
|
|
1182
1615
|
const result = await client.update('work-order-comments', id, buildBody(rest));
|
|
@@ -1202,7 +1635,6 @@ export function registerWriteTools(server, client) {
|
|
|
1202
1635
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
1203
1636
|
content: z.string().min(1).describe('Comment content (required)'),
|
|
1204
1637
|
parent_id: z.string().uuid().optional().describe('Parent comment ID (for threading)'),
|
|
1205
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1206
1638
|
}, async (params) => {
|
|
1207
1639
|
try {
|
|
1208
1640
|
const result = await client.create('project-comments', buildBody(params));
|
|
@@ -1216,7 +1648,6 @@ export function registerWriteTools(server, client) {
|
|
|
1216
1648
|
id: z.string().uuid().describe('Project comment ID'),
|
|
1217
1649
|
content: z.string().min(1).optional().describe('Comment content'),
|
|
1218
1650
|
parent_id: z.string().uuid().optional().describe('Parent comment ID'),
|
|
1219
|
-
user_id: z.string().max(200).optional().describe('User ID of commenter'),
|
|
1220
1651
|
}, async ({ id, ...rest }) => {
|
|
1221
1652
|
try {
|
|
1222
1653
|
const result = await client.update('project-comments', id, buildBody(rest));
|
|
@@ -1238,8 +1669,10 @@ export function registerWriteTools(server, client) {
|
|
|
1238
1669
|
// ============================================================
|
|
1239
1670
|
// 18. Asset Costs (scope: asset_costs)
|
|
1240
1671
|
// ============================================================
|
|
1241
|
-
server.tool('create_asset_cost', 'Create a new asset cost entry. Requires asset_costs:write scope.', {
|
|
1242
|
-
category: z
|
|
1672
|
+
server.tool('create_asset_cost', 'Create a new asset cost entry — the record type shown on the AssetLab "Expenses" page. Requires asset_costs:write scope.', {
|
|
1673
|
+
category: z
|
|
1674
|
+
.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other'])
|
|
1675
|
+
.describe('Cost category (required)'),
|
|
1243
1676
|
amount: z.number().min(0).describe('Cost amount (required)'),
|
|
1244
1677
|
cost_date: z.string().describe('Cost date (ISO 8601, required)'),
|
|
1245
1678
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
@@ -1247,6 +1680,8 @@ export function registerWriteTools(server, client) {
|
|
|
1247
1680
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
1248
1681
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
1249
1682
|
description: z.string().optional().describe('Description'),
|
|
1683
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number (free text)'),
|
|
1684
|
+
po_number: z.string().max(200).optional().describe('Purchase order number (free text)'),
|
|
1250
1685
|
}, async (params) => {
|
|
1251
1686
|
try {
|
|
1252
1687
|
const result = await client.create('asset-costs', buildBody(params));
|
|
@@ -1256,9 +1691,12 @@ export function registerWriteTools(server, client) {
|
|
|
1256
1691
|
return formatError(err);
|
|
1257
1692
|
}
|
|
1258
1693
|
});
|
|
1259
|
-
server.tool('update_asset_cost', 'Update an existing asset cost entry by ID. Requires asset_costs:write scope.', {
|
|
1694
|
+
server.tool('update_asset_cost', 'Update an existing asset cost entry by ID — the record type shown on the AssetLab "Expenses" page. Requires asset_costs:write scope.', {
|
|
1260
1695
|
id: z.string().uuid().describe('Asset cost ID'),
|
|
1261
|
-
category: z
|
|
1696
|
+
category: z
|
|
1697
|
+
.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other'])
|
|
1698
|
+
.optional()
|
|
1699
|
+
.describe('Cost category'),
|
|
1262
1700
|
amount: z.number().min(0).optional().describe('Cost amount'),
|
|
1263
1701
|
cost_date: z.string().optional().describe('Cost date (ISO 8601)'),
|
|
1264
1702
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
@@ -1266,6 +1704,8 @@ export function registerWriteTools(server, client) {
|
|
|
1266
1704
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
1267
1705
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
1268
1706
|
description: z.string().optional().describe('Description'),
|
|
1707
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number (free text)'),
|
|
1708
|
+
po_number: z.string().max(200).optional().describe('Purchase order number (free text)'),
|
|
1269
1709
|
}, async ({ id, ...rest }) => {
|
|
1270
1710
|
try {
|
|
1271
1711
|
const result = await client.update('asset-costs', id, buildBody(rest));
|
|
@@ -1289,10 +1729,18 @@ export function registerWriteTools(server, client) {
|
|
|
1289
1729
|
// ============================================================
|
|
1290
1730
|
server.tool('create_asset_replacement_plan', 'Create a new asset replacement plan. Requires asset_replacement_plans:write scope.', {
|
|
1291
1731
|
asset_id: z.string().uuid().describe('Asset ID (required)'),
|
|
1292
|
-
planned_replacement_year: z
|
|
1732
|
+
planned_replacement_year: z
|
|
1733
|
+
.number()
|
|
1734
|
+
.int()
|
|
1735
|
+
.min(2000)
|
|
1736
|
+
.max(2100)
|
|
1737
|
+
.describe('Planned replacement year (required)'),
|
|
1293
1738
|
estimated_cost: z.number().min(0).optional().describe('Estimated replacement cost'),
|
|
1294
1739
|
priority: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).optional().describe('Priority'),
|
|
1295
|
-
status: z
|
|
1740
|
+
status: z
|
|
1741
|
+
.enum(['PLANNED', 'BUDGETED', 'APPROVED', 'COMPLETED', 'CANCELLED'])
|
|
1742
|
+
.optional()
|
|
1743
|
+
.describe('Status'),
|
|
1296
1744
|
notes: z.string().optional().describe('Notes'),
|
|
1297
1745
|
funding_source: z.string().max(200).optional().describe('Funding source'),
|
|
1298
1746
|
}, async (params) => {
|
|
@@ -1307,10 +1755,19 @@ export function registerWriteTools(server, client) {
|
|
|
1307
1755
|
server.tool('update_asset_replacement_plan', 'Update an existing asset replacement plan by ID. Requires asset_replacement_plans:write scope.', {
|
|
1308
1756
|
id: z.string().uuid().describe('Asset replacement plan ID'),
|
|
1309
1757
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
1310
|
-
planned_replacement_year: z
|
|
1758
|
+
planned_replacement_year: z
|
|
1759
|
+
.number()
|
|
1760
|
+
.int()
|
|
1761
|
+
.min(2000)
|
|
1762
|
+
.max(2100)
|
|
1763
|
+
.optional()
|
|
1764
|
+
.describe('Planned replacement year'),
|
|
1311
1765
|
estimated_cost: z.number().min(0).optional().describe('Estimated replacement cost'),
|
|
1312
1766
|
priority: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).optional().describe('Priority'),
|
|
1313
|
-
status: z
|
|
1767
|
+
status: z
|
|
1768
|
+
.enum(['PLANNED', 'BUDGETED', 'APPROVED', 'COMPLETED', 'CANCELLED'])
|
|
1769
|
+
.optional()
|
|
1770
|
+
.describe('Status'),
|
|
1314
1771
|
notes: z.string().optional().describe('Notes'),
|
|
1315
1772
|
funding_source: z.string().max(200).optional().describe('Funding source'),
|
|
1316
1773
|
}, async ({ id, ...rest }) => {
|
|
@@ -1339,7 +1796,10 @@ export function registerWriteTools(server, client) {
|
|
|
1339
1796
|
title: z.string().min(1).max(500).describe('Task title (required)'),
|
|
1340
1797
|
description: z.string().optional().describe('Description'),
|
|
1341
1798
|
phase_id: z.string().uuid().optional().describe('Phase ID'),
|
|
1342
|
-
status: z
|
|
1799
|
+
status: z
|
|
1800
|
+
.enum(['todo', 'in_progress', 'completed', 'blocked', 'cancelled'])
|
|
1801
|
+
.optional()
|
|
1802
|
+
.describe('Task status'),
|
|
1343
1803
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Priority'),
|
|
1344
1804
|
assigned_to: z.string().max(200).optional().describe('Assigned user'),
|
|
1345
1805
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
@@ -1361,7 +1821,10 @@ export function registerWriteTools(server, client) {
|
|
|
1361
1821
|
title: z.string().min(1).max(500).optional().describe('Task title'),
|
|
1362
1822
|
description: z.string().optional().describe('Description'),
|
|
1363
1823
|
phase_id: z.string().uuid().optional().describe('Phase ID'),
|
|
1364
|
-
status: z
|
|
1824
|
+
status: z
|
|
1825
|
+
.enum(['todo', 'in_progress', 'completed', 'blocked', 'cancelled'])
|
|
1826
|
+
.optional()
|
|
1827
|
+
.describe('Task status'),
|
|
1365
1828
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Priority'),
|
|
1366
1829
|
assigned_to: z.string().max(200).optional().describe('Assigned user'),
|
|
1367
1830
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
@@ -1394,7 +1857,10 @@ export function registerWriteTools(server, client) {
|
|
|
1394
1857
|
name: z.string().min(1).max(500).describe('Milestone name (required)'),
|
|
1395
1858
|
due_date: z.string().describe('Due date (ISO 8601, required)'),
|
|
1396
1859
|
description: z.string().optional().describe('Description'),
|
|
1397
|
-
status: z
|
|
1860
|
+
status: z
|
|
1861
|
+
.enum(['pending', 'completed', 'missed', 'at_risk'])
|
|
1862
|
+
.optional()
|
|
1863
|
+
.describe('Milestone status'),
|
|
1398
1864
|
completed_date: z.string().optional().describe('Completed date (ISO 8601)'),
|
|
1399
1865
|
}, async (params) => {
|
|
1400
1866
|
try {
|
|
@@ -1411,7 +1877,10 @@ export function registerWriteTools(server, client) {
|
|
|
1411
1877
|
name: z.string().min(1).max(500).optional().describe('Milestone name'),
|
|
1412
1878
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
1413
1879
|
description: z.string().optional().describe('Description'),
|
|
1414
|
-
status: z
|
|
1880
|
+
status: z
|
|
1881
|
+
.enum(['pending', 'completed', 'missed', 'at_risk'])
|
|
1882
|
+
.optional()
|
|
1883
|
+
.describe('Milestone status'),
|
|
1415
1884
|
completed_date: z.string().optional().describe('Completed date (ISO 8601)'),
|
|
1416
1885
|
}, async ({ id, ...rest }) => {
|
|
1417
1886
|
try {
|
|
@@ -1438,7 +1907,10 @@ export function registerWriteTools(server, client) {
|
|
|
1438
1907
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
1439
1908
|
name: z.string().min(1).max(500).describe('Phase name (required)'),
|
|
1440
1909
|
description: z.string().optional().describe('Description'),
|
|
1441
|
-
status: z
|
|
1910
|
+
status: z
|
|
1911
|
+
.enum(['pending', 'in_progress', 'completed', 'skipped'])
|
|
1912
|
+
.optional()
|
|
1913
|
+
.describe('Phase status'),
|
|
1442
1914
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
1443
1915
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
1444
1916
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
@@ -1456,7 +1928,10 @@ export function registerWriteTools(server, client) {
|
|
|
1456
1928
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
1457
1929
|
name: z.string().min(1).max(500).optional().describe('Phase name'),
|
|
1458
1930
|
description: z.string().optional().describe('Description'),
|
|
1459
|
-
status: z
|
|
1931
|
+
status: z
|
|
1932
|
+
.enum(['pending', 'in_progress', 'completed', 'skipped'])
|
|
1933
|
+
.optional()
|
|
1934
|
+
.describe('Phase status'),
|
|
1460
1935
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
1461
1936
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
1462
1937
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
@@ -1483,7 +1958,17 @@ export function registerWriteTools(server, client) {
|
|
|
1483
1958
|
// ============================================================
|
|
1484
1959
|
server.tool('create_project_budget_item', 'Create a new project budget item. Requires project_budget_items:write scope.', {
|
|
1485
1960
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
1486
|
-
category: z
|
|
1961
|
+
category: z
|
|
1962
|
+
.enum([
|
|
1963
|
+
'labor',
|
|
1964
|
+
'materials',
|
|
1965
|
+
'equipment',
|
|
1966
|
+
'subcontractors',
|
|
1967
|
+
'permits',
|
|
1968
|
+
'contingency',
|
|
1969
|
+
'other',
|
|
1970
|
+
])
|
|
1971
|
+
.describe('Budget category (required)'),
|
|
1487
1972
|
description: z.string().optional().describe('Description'),
|
|
1488
1973
|
estimated_amount: z.number().min(0).optional().describe('Estimated amount'),
|
|
1489
1974
|
actual_amount: z.number().min(0).optional().describe('Actual amount'),
|
|
@@ -1499,7 +1984,18 @@ export function registerWriteTools(server, client) {
|
|
|
1499
1984
|
server.tool('update_project_budget_item', 'Update an existing project budget item by ID. Requires project_budget_items:write scope.', {
|
|
1500
1985
|
id: z.string().uuid().describe('Project budget item ID'),
|
|
1501
1986
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
1502
|
-
category: z
|
|
1987
|
+
category: z
|
|
1988
|
+
.enum([
|
|
1989
|
+
'labor',
|
|
1990
|
+
'materials',
|
|
1991
|
+
'equipment',
|
|
1992
|
+
'subcontractors',
|
|
1993
|
+
'permits',
|
|
1994
|
+
'contingency',
|
|
1995
|
+
'other',
|
|
1996
|
+
])
|
|
1997
|
+
.optional()
|
|
1998
|
+
.describe('Budget category'),
|
|
1503
1999
|
description: z.string().optional().describe('Description'),
|
|
1504
2000
|
estimated_amount: z.number().min(0).optional().describe('Estimated amount'),
|
|
1505
2001
|
actual_amount: z.number().min(0).optional().describe('Actual amount'),
|
|
@@ -1810,7 +2306,13 @@ export function registerWriteTools(server, client) {
|
|
|
1810
2306
|
server.tool('create_project_phase_category', 'Create a new project phase category. Requires project_phase_categories:write scope.', {
|
|
1811
2307
|
name: z.string().min(1).max(500).describe('Phase name (required), e.g. "Planning", "Design"'),
|
|
1812
2308
|
description: z.string().max(2000).optional().describe('Description of the phase'),
|
|
1813
|
-
sort_order: z
|
|
2309
|
+
sort_order: z
|
|
2310
|
+
.number()
|
|
2311
|
+
.int()
|
|
2312
|
+
.min(0)
|
|
2313
|
+
.max(10000)
|
|
2314
|
+
.optional()
|
|
2315
|
+
.describe('Display order (lower = first)'),
|
|
1814
2316
|
}, async (params) => {
|
|
1815
2317
|
try {
|
|
1816
2318
|
const result = await client.create('project-phase-categories', buildBody(params));
|
|
@@ -1824,7 +2326,13 @@ export function registerWriteTools(server, client) {
|
|
|
1824
2326
|
id: z.string().uuid().describe('Project phase category ID'),
|
|
1825
2327
|
name: z.string().min(1).max(500).optional().describe('Phase name'),
|
|
1826
2328
|
description: z.string().max(2000).optional().describe('Description'),
|
|
1827
|
-
sort_order: z
|
|
2329
|
+
sort_order: z
|
|
2330
|
+
.number()
|
|
2331
|
+
.int()
|
|
2332
|
+
.min(0)
|
|
2333
|
+
.max(10000)
|
|
2334
|
+
.optional()
|
|
2335
|
+
.describe('Display order (lower = first)'),
|
|
1828
2336
|
}, async ({ id, ...rest }) => {
|
|
1829
2337
|
try {
|
|
1830
2338
|
const result = await client.update('project-phase-categories', id, buildBody(rest));
|
|
@@ -2129,9 +2637,15 @@ export function registerWriteTools(server, client) {
|
|
|
2129
2637
|
// 35. Custom Field Definitions (scope: custom_fields)
|
|
2130
2638
|
// ============================================================
|
|
2131
2639
|
server.tool('create_custom_field_definition', 'Create a new custom field definition. Requires custom_fields:write scope.', {
|
|
2132
|
-
entity_type: z
|
|
2640
|
+
entity_type: z
|
|
2641
|
+
.string()
|
|
2642
|
+
.min(1)
|
|
2643
|
+
.max(100)
|
|
2644
|
+
.describe('Entity type this field applies to (required)'),
|
|
2133
2645
|
field_name: z.string().min(1).max(200).describe('Field name / key (required)'),
|
|
2134
|
-
field_type: z
|
|
2646
|
+
field_type: z
|
|
2647
|
+
.enum(['text', 'number', 'date', 'boolean', 'select'])
|
|
2648
|
+
.describe('Field data type (required)'),
|
|
2135
2649
|
field_label: z.string().max(200).optional().describe('Display label'),
|
|
2136
2650
|
options: z.array(z.string()).optional().describe('Options for select-type fields'),
|
|
2137
2651
|
is_required: z.boolean().optional().describe('Whether the field is required'),
|
|
@@ -2149,7 +2663,10 @@ export function registerWriteTools(server, client) {
|
|
|
2149
2663
|
id: z.string().uuid().describe('Custom field definition ID'),
|
|
2150
2664
|
entity_type: z.string().min(1).max(100).optional().describe('Entity type'),
|
|
2151
2665
|
field_name: z.string().min(1).max(200).optional().describe('Field name / key'),
|
|
2152
|
-
field_type: z
|
|
2666
|
+
field_type: z
|
|
2667
|
+
.enum(['text', 'number', 'date', 'boolean', 'select'])
|
|
2668
|
+
.optional()
|
|
2669
|
+
.describe('Field data type'),
|
|
2153
2670
|
field_label: z.string().max(200).optional().describe('Display label'),
|
|
2154
2671
|
options: z.array(z.string()).optional().describe('Options for select-type fields'),
|
|
2155
2672
|
is_required: z.boolean().optional().describe('Whether the field is required'),
|
|
@@ -2175,14 +2692,38 @@ export function registerWriteTools(server, client) {
|
|
|
2175
2692
|
// ============================================================
|
|
2176
2693
|
// 36. Custom Field Values (scope: custom_fields)
|
|
2177
2694
|
// ============================================================
|
|
2178
|
-
server.tool('create_custom_field_value',
|
|
2695
|
+
server.tool('create_custom_field_value', "Create (upsert) a custom field value for an entity. The table stores values in typed columns — prefer setting the one that matches the field definition's field_type: value_text (text/select), value_number (number), value_date (date, ISO YYYY-MM-DD), value_boolean (boolean). Alternatively pass a single `value` string and the server will dispatch it to the right column based on field_type. Writes upsert on (entity_id, field_definition_id) so replaying a batch is idempotent. Requires custom_fields:write scope.", {
|
|
2179
2696
|
entity_id: z.string().uuid().describe('Entity ID — e.g. asset.id, work_order.id (required)'),
|
|
2180
|
-
field_definition_id: z
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2697
|
+
field_definition_id: z
|
|
2698
|
+
.string()
|
|
2699
|
+
.uuid()
|
|
2700
|
+
.describe('Custom field definition ID — resolve via list_custom_field_definitions (required)'),
|
|
2701
|
+
value_text: z
|
|
2702
|
+
.string()
|
|
2703
|
+
.max(5000)
|
|
2704
|
+
.nullable()
|
|
2705
|
+
.optional()
|
|
2706
|
+
.describe('Text value (use for field_type=text or select)'),
|
|
2707
|
+
value_number: z
|
|
2708
|
+
.number()
|
|
2709
|
+
.nullable()
|
|
2710
|
+
.optional()
|
|
2711
|
+
.describe('Numeric value (use for field_type=number)'),
|
|
2712
|
+
value_date: z
|
|
2713
|
+
.string()
|
|
2714
|
+
.nullable()
|
|
2715
|
+
.optional()
|
|
2716
|
+
.describe('Date value, ISO YYYY-MM-DD (use for field_type=date)'),
|
|
2717
|
+
value_boolean: z
|
|
2718
|
+
.boolean()
|
|
2719
|
+
.nullable()
|
|
2720
|
+
.optional()
|
|
2721
|
+
.describe('Boolean value (use for field_type=boolean)'),
|
|
2722
|
+
value: z
|
|
2723
|
+
.string()
|
|
2724
|
+
.max(5000)
|
|
2725
|
+
.optional()
|
|
2726
|
+
.describe("Legacy single-value shim — server dispatches to the correct typed column based on the field definition's field_type. Ignored if any value_* typed column is set."),
|
|
2186
2727
|
}, async (params) => {
|
|
2187
2728
|
try {
|
|
2188
2729
|
const result = await client.create('custom-field-values', buildBody(params));
|
|
@@ -2192,15 +2733,23 @@ export function registerWriteTools(server, client) {
|
|
|
2192
2733
|
return formatError(err);
|
|
2193
2734
|
}
|
|
2194
2735
|
});
|
|
2195
|
-
server.tool('update_custom_field_value',
|
|
2736
|
+
server.tool('update_custom_field_value', "Update an existing custom field value by ID. Set whichever typed column matches the definition's field_type (value_text / value_number / value_date / value_boolean), or pass a single `value` and the server will dispatch it. Requires custom_fields:write scope.", {
|
|
2196
2737
|
id: z.string().uuid().describe('Custom field value ID'),
|
|
2197
2738
|
entity_id: z.string().uuid().optional().describe('Entity ID'),
|
|
2198
|
-
field_definition_id: z
|
|
2739
|
+
field_definition_id: z
|
|
2740
|
+
.string()
|
|
2741
|
+
.uuid()
|
|
2742
|
+
.optional()
|
|
2743
|
+
.describe('Custom field definition ID — required when using the `value` fallback if you want to avoid the server fetching it'),
|
|
2199
2744
|
value_text: z.string().max(5000).nullable().optional().describe('Text value'),
|
|
2200
2745
|
value_number: z.number().nullable().optional().describe('Numeric value'),
|
|
2201
2746
|
value_date: z.string().nullable().optional().describe('Date value, ISO YYYY-MM-DD'),
|
|
2202
2747
|
value_boolean: z.boolean().nullable().optional().describe('Boolean value'),
|
|
2203
|
-
value: z
|
|
2748
|
+
value: z
|
|
2749
|
+
.string()
|
|
2750
|
+
.max(5000)
|
|
2751
|
+
.optional()
|
|
2752
|
+
.describe('Legacy single-value shim — server dispatches based on field_type'),
|
|
2204
2753
|
}, async ({ id, ...rest }) => {
|
|
2205
2754
|
try {
|
|
2206
2755
|
const result = await client.update('custom-field-values', id, buildBody(rest));
|
|
@@ -2226,11 +2775,27 @@ export function registerWriteTools(server, client) {
|
|
|
2226
2775
|
name: z.string().min(1).max(500).describe('Part name (required)'),
|
|
2227
2776
|
part_number: z.string().max(200).optional().describe('Part number / SKU'),
|
|
2228
2777
|
category: z.string().max(200).optional().describe('Category label'),
|
|
2229
|
-
supplier: z
|
|
2778
|
+
supplier: z
|
|
2779
|
+
.string()
|
|
2780
|
+
.max(500)
|
|
2781
|
+
.optional()
|
|
2782
|
+
.describe('DEPRECATED — legacy free-text supplier name. Use supplier_id instead.'),
|
|
2230
2783
|
supplier_id: z.string().uuid().optional().describe('Vendor ID (resolve via list_vendors)'),
|
|
2231
2784
|
cost: z.number().min(0).optional().describe('Unit cost'),
|
|
2232
|
-
quantity: z
|
|
2233
|
-
|
|
2785
|
+
quantity: z
|
|
2786
|
+
.number()
|
|
2787
|
+
.int()
|
|
2788
|
+
.min(0)
|
|
2789
|
+
.max(10_000_000)
|
|
2790
|
+
.optional()
|
|
2791
|
+
.describe('Current stock quantity'),
|
|
2792
|
+
desired_quantity: z
|
|
2793
|
+
.number()
|
|
2794
|
+
.int()
|
|
2795
|
+
.min(0)
|
|
2796
|
+
.max(10_000_000)
|
|
2797
|
+
.optional()
|
|
2798
|
+
.describe('Target / reorder quantity'),
|
|
2234
2799
|
specific_location: z.string().max(500).optional().describe('Storage location description'),
|
|
2235
2800
|
site_id: z.string().uuid().optional().describe('Site ID'),
|
|
2236
2801
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
@@ -2249,11 +2814,27 @@ export function registerWriteTools(server, client) {
|
|
|
2249
2814
|
name: z.string().min(1).max(500).optional().describe('Part name'),
|
|
2250
2815
|
part_number: z.string().max(200).optional().describe('Part number / SKU'),
|
|
2251
2816
|
category: z.string().max(200).optional().describe('Category label'),
|
|
2252
|
-
supplier: z
|
|
2817
|
+
supplier: z
|
|
2818
|
+
.string()
|
|
2819
|
+
.max(500)
|
|
2820
|
+
.optional()
|
|
2821
|
+
.describe('DEPRECATED — legacy free-text supplier name. Use supplier_id instead.'),
|
|
2253
2822
|
supplier_id: z.string().uuid().optional().describe('Vendor ID (resolve via list_vendors)'),
|
|
2254
2823
|
cost: z.number().min(0).optional().describe('Unit cost'),
|
|
2255
|
-
quantity: z
|
|
2256
|
-
|
|
2824
|
+
quantity: z
|
|
2825
|
+
.number()
|
|
2826
|
+
.int()
|
|
2827
|
+
.min(0)
|
|
2828
|
+
.max(10_000_000)
|
|
2829
|
+
.optional()
|
|
2830
|
+
.describe('Current stock quantity'),
|
|
2831
|
+
desired_quantity: z
|
|
2832
|
+
.number()
|
|
2833
|
+
.int()
|
|
2834
|
+
.min(0)
|
|
2835
|
+
.max(10_000_000)
|
|
2836
|
+
.optional()
|
|
2837
|
+
.describe('Target / reorder quantity'),
|
|
2257
2838
|
specific_location: z.string().max(500).optional().describe('Storage location description'),
|
|
2258
2839
|
site_id: z.string().uuid().optional().describe('Site ID'),
|
|
2259
2840
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
@@ -2280,7 +2861,15 @@ export function registerWriteTools(server, client) {
|
|
|
2280
2861
|
// Upload URLs
|
|
2281
2862
|
// ============================================================
|
|
2282
2863
|
server.tool('create_upload_url', 'Generate a signed upload URL for uploading a file to AssetLab storage. Returns a signed_url to PUT the file to, a public_url for referencing, and the path. IMPORTANT — When a user wants to upload a file, always clarify the target. File upload paths: (1) Asset IMAGE: bucket "asset-images" → update_asset with image_url. (2) Asset DOCUMENT (O&M, warranty, spec): bucket "documents" → create_asset_document. (3) Work order IMAGE: bucket "attachments" → update_work_order with image_url. (4) Work order/request/PM ATTACHMENT: bucket "attachments" → create_attachment with the parent ID. (5) Project DOCUMENT: bucket "project-documents" → create_project_document. (6) Contract DOCUMENT: bucket "contract-documents" → create_contract_document. Always ask the user which type they mean if ambiguous. Requires upload_urls:write scope.', {
|
|
2283
|
-
bucket: z
|
|
2864
|
+
bucket: z
|
|
2865
|
+
.enum([
|
|
2866
|
+
'documents',
|
|
2867
|
+
'attachments',
|
|
2868
|
+
'project-documents',
|
|
2869
|
+
'contract-documents',
|
|
2870
|
+
'asset-images',
|
|
2871
|
+
])
|
|
2872
|
+
.describe('Storage bucket (required). Use "asset-images" for asset photos.'),
|
|
2284
2873
|
file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
|
|
2285
2874
|
}, async (params) => {
|
|
2286
2875
|
try {
|
|
@@ -2292,10 +2881,25 @@ export function registerWriteTools(server, client) {
|
|
|
2292
2881
|
}
|
|
2293
2882
|
});
|
|
2294
2883
|
server.tool('upload_file', 'Upload a file to AssetLab storage by sending its bytes inline (base64). The AssetLab backend performs the storage upload server-side — use this tool when the client cannot PUT directly to Supabase Storage (e.g. Claude integrations whose outbound network blocks arbitrary supabase.co hosts). Returns { path, public_url, bucket, file_size, content_type }. After uploading, pass path or public_url to the appropriate record tool (update_asset image_url, create_asset_document file_path, update_work_order image_url, create_attachment file_path, create_project_document file_path, create_contract_document file_path). Server limit is ~10 MB decoded; MCP arg ceiling effectively caps file size around 700 KB–1 MB. For larger files, use create_upload_url instead. Requires upload_urls:write scope.', {
|
|
2295
|
-
bucket: z
|
|
2884
|
+
bucket: z
|
|
2885
|
+
.enum([
|
|
2886
|
+
'documents',
|
|
2887
|
+
'attachments',
|
|
2888
|
+
'project-documents',
|
|
2889
|
+
'contract-documents',
|
|
2890
|
+
'asset-images',
|
|
2891
|
+
])
|
|
2892
|
+
.describe('Storage bucket (required). Use "asset-images" for asset photos, "attachments" for work-order/PM attachments.'),
|
|
2296
2893
|
file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
|
|
2297
|
-
content_base64: z
|
|
2298
|
-
|
|
2894
|
+
content_base64: z
|
|
2895
|
+
.string()
|
|
2896
|
+
.min(1)
|
|
2897
|
+
.describe('File contents base64-encoded (required). Data URI prefixes like "data:image/png;base64," are stripped automatically.'),
|
|
2898
|
+
content_type: z
|
|
2899
|
+
.string()
|
|
2900
|
+
.max(200)
|
|
2901
|
+
.optional()
|
|
2902
|
+
.describe('MIME type (e.g. image/jpeg, application/pdf). Defaults to application/octet-stream.'),
|
|
2299
2903
|
}, async (params) => {
|
|
2300
2904
|
try {
|
|
2301
2905
|
const result = await client.create('upload-files', buildBody(params));
|
|
@@ -2310,9 +2914,16 @@ export function registerWriteTools(server, client) {
|
|
|
2310
2914
|
// ============================================================
|
|
2311
2915
|
server.tool('create_asset_document', 'Create an asset document record (after uploading the file via create_upload_url). Requires asset_documents:write scope.', {
|
|
2312
2916
|
name: z.string().min(1).max(500).describe('Document name (required)'),
|
|
2313
|
-
file_path: z
|
|
2917
|
+
file_path: z
|
|
2918
|
+
.string()
|
|
2919
|
+
.min(1)
|
|
2920
|
+
.max(2000)
|
|
2921
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2314
2922
|
asset_id: z.string().uuid().describe('Asset ID this document belongs to (required)'),
|
|
2315
|
-
category: z
|
|
2923
|
+
category: z
|
|
2924
|
+
.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other'])
|
|
2925
|
+
.optional()
|
|
2926
|
+
.describe('Document category'),
|
|
2316
2927
|
description: z.string().optional().describe('Description'),
|
|
2317
2928
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2318
2929
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
@@ -2331,7 +2942,10 @@ export function registerWriteTools(server, client) {
|
|
|
2331
2942
|
name: z.string().min(1).max(500).optional().describe('Document name'),
|
|
2332
2943
|
file_path: z.string().max(2000).optional().describe('Storage path'),
|
|
2333
2944
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
2334
|
-
category: z
|
|
2945
|
+
category: z
|
|
2946
|
+
.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other'])
|
|
2947
|
+
.optional()
|
|
2948
|
+
.describe('Document category'),
|
|
2335
2949
|
description: z.string().optional().describe('Description'),
|
|
2336
2950
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2337
2951
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
@@ -2364,10 +2978,26 @@ export function registerWriteTools(server, client) {
|
|
|
2364
2978
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2365
2979
|
uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
|
|
2366
2980
|
description: z.string().optional().describe('Description'),
|
|
2367
|
-
work_order_id: z
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2981
|
+
work_order_id: z
|
|
2982
|
+
.string()
|
|
2983
|
+
.uuid()
|
|
2984
|
+
.optional()
|
|
2985
|
+
.describe('Work order ID (exactly one parent required)'),
|
|
2986
|
+
work_request_id: z
|
|
2987
|
+
.string()
|
|
2988
|
+
.uuid()
|
|
2989
|
+
.optional()
|
|
2990
|
+
.describe('Work request ID (exactly one parent required)'),
|
|
2991
|
+
pm_schedule_id: z
|
|
2992
|
+
.string()
|
|
2993
|
+
.uuid()
|
|
2994
|
+
.optional()
|
|
2995
|
+
.describe('PM schedule ID (exactly one parent required)'),
|
|
2996
|
+
pm_template_id: z
|
|
2997
|
+
.string()
|
|
2998
|
+
.uuid()
|
|
2999
|
+
.optional()
|
|
3000
|
+
.describe('PM template ID (exactly one parent required)'),
|
|
2371
3001
|
}, async (params) => {
|
|
2372
3002
|
try {
|
|
2373
3003
|
const result = await client.create('attachments', buildBody(params));
|
|
@@ -2413,7 +3043,11 @@ export function registerWriteTools(server, client) {
|
|
|
2413
3043
|
server.tool('create_project_document', 'Create a project document record. Requires project_documents:write scope.', {
|
|
2414
3044
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2415
3045
|
name: z.string().min(1).max(500).describe('Document name (required)'),
|
|
2416
|
-
file_path: z
|
|
3046
|
+
file_path: z
|
|
3047
|
+
.string()
|
|
3048
|
+
.min(1)
|
|
3049
|
+
.max(2000)
|
|
3050
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2417
3051
|
uploaded_by: z.string().min(1).max(200).describe('Uploader user ID (required)'),
|
|
2418
3052
|
folder_id: z.string().uuid().optional().describe('Folder ID'),
|
|
2419
3053
|
description: z.string().optional().describe('Description'),
|
|
@@ -2462,7 +3096,11 @@ export function registerWriteTools(server, client) {
|
|
|
2462
3096
|
server.tool('create_contract_document', 'Create a contract document record. Requires contract_documents:write scope.', {
|
|
2463
3097
|
contract_id: z.string().uuid().describe('Contract ID (required)'),
|
|
2464
3098
|
file_name: z.string().min(1).max(500).describe('File name (required)'),
|
|
2465
|
-
file_path: z
|
|
3099
|
+
file_path: z
|
|
3100
|
+
.string()
|
|
3101
|
+
.min(1)
|
|
3102
|
+
.max(2000)
|
|
3103
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2466
3104
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
2467
3105
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2468
3106
|
uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
|
|
@@ -2554,7 +3192,10 @@ export function registerWriteTools(server, client) {
|
|
|
2554
3192
|
server.tool('create_project_task_dependency', 'Create a dependency between two project tasks. Requires project_task_dependencies:write scope.', {
|
|
2555
3193
|
task_id: z.string().uuid().describe('Task ID (the dependent task, required)'),
|
|
2556
3194
|
depends_on_task_id: z.string().uuid().describe('Task ID that must complete first (required)'),
|
|
2557
|
-
dependency_type: z
|
|
3195
|
+
dependency_type: z
|
|
3196
|
+
.enum(['finish_to_start', 'start_to_start', 'finish_to_finish', 'start_to_finish'])
|
|
3197
|
+
.optional()
|
|
3198
|
+
.describe('Dependency type (default: finish_to_start)'),
|
|
2558
3199
|
}, async (params) => {
|
|
2559
3200
|
try {
|
|
2560
3201
|
const result = await client.create('project-task-dependencies', buildBody(params));
|
|
@@ -2579,9 +3220,20 @@ export function registerWriteTools(server, client) {
|
|
|
2579
3220
|
server.tool('create_project_update', 'Create a periodic project status update. Requires project_updates:write scope.', {
|
|
2580
3221
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2581
3222
|
author_id: z.string().min(1).max(200).describe('Author Clerk user ID (required)'),
|
|
2582
|
-
timeframe: z
|
|
2583
|
-
|
|
2584
|
-
|
|
3223
|
+
timeframe: z
|
|
3224
|
+
.enum(['monthly', 'quarterly', 'bi-annually', 'annually'])
|
|
3225
|
+
.describe('Update timeframe (required)'),
|
|
3226
|
+
period_year: z
|
|
3227
|
+
.number()
|
|
3228
|
+
.int()
|
|
3229
|
+
.min(2000)
|
|
3230
|
+
.max(2100)
|
|
3231
|
+
.describe('Year for this update period (required)'),
|
|
3232
|
+
period_value: z
|
|
3233
|
+
.string()
|
|
3234
|
+
.min(1)
|
|
3235
|
+
.max(20)
|
|
3236
|
+
.describe('Period value — 1-12 for monthly, 1-4 for quarterly, etc. (required)'),
|
|
2585
3237
|
content: z.string().min(1).describe('Update content (required)'),
|
|
2586
3238
|
title: z.string().max(500).optional().describe('Optional custom title'),
|
|
2587
3239
|
}, async (params) => {
|
|
@@ -2597,8 +3249,17 @@ export function registerWriteTools(server, client) {
|
|
|
2597
3249
|
id: z.string().uuid().describe('Project update ID'),
|
|
2598
3250
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
2599
3251
|
author_id: z.string().min(1).max(200).optional().describe('Author Clerk user ID'),
|
|
2600
|
-
timeframe: z
|
|
2601
|
-
|
|
3252
|
+
timeframe: z
|
|
3253
|
+
.enum(['monthly', 'quarterly', 'bi-annually', 'annually'])
|
|
3254
|
+
.optional()
|
|
3255
|
+
.describe('Update timeframe'),
|
|
3256
|
+
period_year: z
|
|
3257
|
+
.number()
|
|
3258
|
+
.int()
|
|
3259
|
+
.min(2000)
|
|
3260
|
+
.max(2100)
|
|
3261
|
+
.optional()
|
|
3262
|
+
.describe('Year for this update period'),
|
|
2602
3263
|
period_value: z.string().min(1).max(20).optional().describe('Period value'),
|
|
2603
3264
|
content: z.string().min(1).optional().describe('Update content'),
|
|
2604
3265
|
title: z.string().max(500).optional().describe('Optional custom title'),
|
|
@@ -2629,7 +3290,12 @@ export function registerWriteTools(server, client) {
|
|
|
2629
3290
|
total_budget: z.number().min(0).describe('Total budget amount (required)'),
|
|
2630
3291
|
actual_cost: z.number().min(0).describe('Actual cost to date (required)'),
|
|
2631
3292
|
forecasted_cost: z.number().min(0).optional().describe('Forecasted total cost'),
|
|
2632
|
-
percent_complete: z
|
|
3293
|
+
percent_complete: z
|
|
3294
|
+
.number()
|
|
3295
|
+
.min(0)
|
|
3296
|
+
.max(100)
|
|
3297
|
+
.optional()
|
|
3298
|
+
.describe('Completion percentage (0-100)'),
|
|
2633
3299
|
}, async (params) => {
|
|
2634
3300
|
try {
|
|
2635
3301
|
const result = await client.create('project-cost-snapshots', buildBody(params));
|
|
@@ -2799,10 +3465,16 @@ export function registerWriteTools(server, client) {
|
|
|
2799
3465
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2800
3466
|
title: z.string().min(1).max(500).describe('Risk title (required)'),
|
|
2801
3467
|
description: z.string().optional().describe('Risk description'),
|
|
2802
|
-
category: z
|
|
3468
|
+
category: z
|
|
3469
|
+
.enum(['technical', 'financial', 'schedule', 'resource', 'external'])
|
|
3470
|
+
.optional()
|
|
3471
|
+
.describe('Risk category'),
|
|
2803
3472
|
probability: z.enum(['low', 'medium', 'high']).optional().describe('Probability level'),
|
|
2804
3473
|
impact: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Impact level'),
|
|
2805
|
-
status: z
|
|
3474
|
+
status: z
|
|
3475
|
+
.enum(['identified', 'analyzing', 'mitigating', 'resolved', 'accepted'])
|
|
3476
|
+
.optional()
|
|
3477
|
+
.describe('Risk status (default: identified)'),
|
|
2806
3478
|
mitigation_plan: z.string().optional().describe('Mitigation plan'),
|
|
2807
3479
|
contingency_plan: z.string().optional().describe('Contingency plan'),
|
|
2808
3480
|
owner_id: z.string().max(200).optional().describe('Risk owner (Clerk user ID)'),
|
|
@@ -2822,10 +3494,16 @@ export function registerWriteTools(server, client) {
|
|
|
2822
3494
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
2823
3495
|
title: z.string().min(1).max(500).optional().describe('Risk title'),
|
|
2824
3496
|
description: z.string().optional().describe('Risk description'),
|
|
2825
|
-
category: z
|
|
3497
|
+
category: z
|
|
3498
|
+
.enum(['technical', 'financial', 'schedule', 'resource', 'external'])
|
|
3499
|
+
.optional()
|
|
3500
|
+
.describe('Risk category'),
|
|
2826
3501
|
probability: z.enum(['low', 'medium', 'high']).optional().describe('Probability level'),
|
|
2827
3502
|
impact: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Impact level'),
|
|
2828
|
-
status: z
|
|
3503
|
+
status: z
|
|
3504
|
+
.enum(['identified', 'analyzing', 'mitigating', 'resolved', 'accepted'])
|
|
3505
|
+
.optional()
|
|
3506
|
+
.describe('Risk status'),
|
|
2829
3507
|
mitigation_plan: z.string().optional().describe('Mitigation plan'),
|
|
2830
3508
|
contingency_plan: z.string().optional().describe('Contingency plan'),
|
|
2831
3509
|
owner_id: z.string().max(200).optional().describe('Risk owner (Clerk user ID)'),
|
|
@@ -2881,7 +3559,10 @@ export function registerWriteTools(server, client) {
|
|
|
2881
3559
|
icon: z.string().max(200).optional().describe('Icon name (e.g., "droplets" for water)'),
|
|
2882
3560
|
color: z.string().max(50).optional().describe('Hex color code (e.g., "#3B82F6")'),
|
|
2883
3561
|
sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
|
|
2884
|
-
is_active: z
|
|
3562
|
+
is_active: z
|
|
3563
|
+
.boolean()
|
|
3564
|
+
.optional()
|
|
3565
|
+
.describe('Whether the service area is active (default: true)'),
|
|
2885
3566
|
}, async (params) => {
|
|
2886
3567
|
try {
|
|
2887
3568
|
const result = await client.create('service-areas', buildBody(params));
|
|
@@ -2971,24 +3652,65 @@ export function registerWriteTools(server, client) {
|
|
|
2971
3652
|
server.tool('create_los_measure', 'Create a new LoS measure within a service area. Requires los_measures:write scope. Resolve service_area_id first via list_service_areas.', {
|
|
2972
3653
|
service_area_id: z.string().uuid().describe('Service area ID (required)'),
|
|
2973
3654
|
name: z.string().min(1).max(500).describe('Measure name (required, unique per service area)'),
|
|
2974
|
-
category: z
|
|
3655
|
+
category: z
|
|
3656
|
+
.enum([
|
|
3657
|
+
'quality',
|
|
3658
|
+
'reliability',
|
|
3659
|
+
'responsiveness',
|
|
3660
|
+
'safety',
|
|
3661
|
+
'sustainability',
|
|
3662
|
+
'cost_efficiency',
|
|
3663
|
+
'capacity',
|
|
3664
|
+
])
|
|
3665
|
+
.describe('Measure category (required)'),
|
|
2975
3666
|
type: z.enum(['community', 'technical']).describe('Measure type (required)'),
|
|
2976
|
-
data_source: z
|
|
2977
|
-
|
|
2978
|
-
'
|
|
2979
|
-
'
|
|
2980
|
-
'
|
|
3667
|
+
data_source: z
|
|
3668
|
+
.enum([
|
|
3669
|
+
'manual',
|
|
3670
|
+
'custom_formula',
|
|
3671
|
+
'asset_condition_avg',
|
|
3672
|
+
'asset_condition_pct_above',
|
|
3673
|
+
'asset_condition_pct_below',
|
|
3674
|
+
'risk_score_avg',
|
|
3675
|
+
'risk_pct_critical',
|
|
3676
|
+
'wo_response_time_avg',
|
|
3677
|
+
'wo_completion_time_avg',
|
|
3678
|
+
'wo_backlog_count',
|
|
3679
|
+
'wo_overdue_count',
|
|
3680
|
+
'pm_compliance_rate',
|
|
3681
|
+
'compliance_score',
|
|
3682
|
+
'fci',
|
|
3683
|
+
'deferred_maintenance_ratio',
|
|
2981
3684
|
'asset_past_useful_life_pct',
|
|
2982
|
-
])
|
|
3685
|
+
])
|
|
3686
|
+
.describe('Data source type (required). Use "manual" if values will be entered by hand.'),
|
|
2983
3687
|
description: z.string().max(2000).optional().describe('Description'),
|
|
2984
|
-
community_statement: z
|
|
2985
|
-
|
|
2986
|
-
|
|
3688
|
+
community_statement: z
|
|
3689
|
+
.string()
|
|
3690
|
+
.max(2000)
|
|
3691
|
+
.optional()
|
|
3692
|
+
.describe('Community-facing statement (for community type measures)'),
|
|
3693
|
+
unit: z
|
|
3694
|
+
.string()
|
|
3695
|
+
.max(100)
|
|
3696
|
+
.optional()
|
|
3697
|
+
.describe('Unit of measurement (e.g., "%", "hours", "count")'),
|
|
3698
|
+
trend_direction: z
|
|
3699
|
+
.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal'])
|
|
3700
|
+
.optional()
|
|
3701
|
+
.describe('Which direction is better'),
|
|
2987
3702
|
target_value: z.number().optional().describe('Target value'),
|
|
2988
3703
|
minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
|
|
2989
3704
|
stretch_goal: z.number().optional().describe('Stretch goal value'),
|
|
2990
|
-
weight: z
|
|
2991
|
-
|
|
3705
|
+
weight: z
|
|
3706
|
+
.number()
|
|
3707
|
+
.min(0)
|
|
3708
|
+
.optional()
|
|
3709
|
+
.describe('Weight for composite score calculation (default: 1.0)'),
|
|
3710
|
+
data_source_config: z
|
|
3711
|
+
.record(z.unknown())
|
|
3712
|
+
.optional()
|
|
3713
|
+
.describe('Data source configuration (JSONB). E.g., {"threshold": 3} for pct_above/below, {"days_back": 90} for WO metrics.'),
|
|
2992
3714
|
is_active: z.boolean().optional().describe('Whether the measure is active (default: true)'),
|
|
2993
3715
|
sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
|
|
2994
3716
|
}, async (params) => {
|
|
@@ -3003,24 +3725,55 @@ export function registerWriteTools(server, client) {
|
|
|
3003
3725
|
server.tool('update_los_measure', 'Update an existing LoS measure by ID. Requires los_measures:write scope.', {
|
|
3004
3726
|
id: z.string().uuid().describe('LoS measure ID'),
|
|
3005
3727
|
name: z.string().min(1).max(500).optional().describe('Measure name'),
|
|
3006
|
-
category: z
|
|
3728
|
+
category: z
|
|
3729
|
+
.enum([
|
|
3730
|
+
'quality',
|
|
3731
|
+
'reliability',
|
|
3732
|
+
'responsiveness',
|
|
3733
|
+
'safety',
|
|
3734
|
+
'sustainability',
|
|
3735
|
+
'cost_efficiency',
|
|
3736
|
+
'capacity',
|
|
3737
|
+
])
|
|
3738
|
+
.optional()
|
|
3739
|
+
.describe('Measure category'),
|
|
3007
3740
|
type: z.enum(['community', 'technical']).optional().describe('Measure type'),
|
|
3008
|
-
data_source: z
|
|
3009
|
-
|
|
3010
|
-
'
|
|
3011
|
-
'
|
|
3012
|
-
'
|
|
3741
|
+
data_source: z
|
|
3742
|
+
.enum([
|
|
3743
|
+
'manual',
|
|
3744
|
+
'custom_formula',
|
|
3745
|
+
'asset_condition_avg',
|
|
3746
|
+
'asset_condition_pct_above',
|
|
3747
|
+
'asset_condition_pct_below',
|
|
3748
|
+
'risk_score_avg',
|
|
3749
|
+
'risk_pct_critical',
|
|
3750
|
+
'wo_response_time_avg',
|
|
3751
|
+
'wo_completion_time_avg',
|
|
3752
|
+
'wo_backlog_count',
|
|
3753
|
+
'wo_overdue_count',
|
|
3754
|
+
'pm_compliance_rate',
|
|
3755
|
+
'compliance_score',
|
|
3756
|
+
'fci',
|
|
3757
|
+
'deferred_maintenance_ratio',
|
|
3013
3758
|
'asset_past_useful_life_pct',
|
|
3014
|
-
])
|
|
3759
|
+
])
|
|
3760
|
+
.optional()
|
|
3761
|
+
.describe('Data source type'),
|
|
3015
3762
|
description: z.string().max(2000).optional().describe('Description'),
|
|
3016
3763
|
community_statement: z.string().max(2000).optional().describe('Community-facing statement'),
|
|
3017
3764
|
unit: z.string().max(100).optional().describe('Unit of measurement'),
|
|
3018
|
-
trend_direction: z
|
|
3765
|
+
trend_direction: z
|
|
3766
|
+
.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal'])
|
|
3767
|
+
.optional()
|
|
3768
|
+
.describe('Which direction is better'),
|
|
3019
3769
|
target_value: z.number().optional().describe('Target value'),
|
|
3020
3770
|
minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
|
|
3021
3771
|
stretch_goal: z.number().optional().describe('Stretch goal value'),
|
|
3022
3772
|
weight: z.number().min(0).optional().describe('Weight for composite score calculation'),
|
|
3023
|
-
data_source_config: z
|
|
3773
|
+
data_source_config: z
|
|
3774
|
+
.record(z.unknown())
|
|
3775
|
+
.optional()
|
|
3776
|
+
.describe('Data source configuration (JSONB)'),
|
|
3024
3777
|
is_active: z.boolean().optional().describe('Active status'),
|
|
3025
3778
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
3026
3779
|
}, async ({ id, ...rest }) => {
|
|
@@ -3046,12 +3799,19 @@ export function registerWriteTools(server, client) {
|
|
|
3046
3799
|
// ============================================================
|
|
3047
3800
|
server.tool('create_los_measurement', 'Record a new LoS measurement value. Requires los_measurements:write scope. Resolve los_measure_id first via list_los_measures.', {
|
|
3048
3801
|
los_measure_id: z.string().uuid().describe('LoS measure ID (required)'),
|
|
3049
|
-
period_type: z
|
|
3050
|
-
|
|
3802
|
+
period_type: z
|
|
3803
|
+
.enum(['monthly', 'quarterly', 'semi_annual', 'annual'])
|
|
3804
|
+
.describe('Period type (required)'),
|
|
3805
|
+
period_start: z
|
|
3806
|
+
.string()
|
|
3807
|
+
.describe('Period start date (ISO 8601, required, e.g., "2026-01-01")'),
|
|
3051
3808
|
period_end: z.string().describe('Period end date (ISO 8601, required, e.g., "2026-03-31")'),
|
|
3052
3809
|
actual_value: z.number().describe('Measured value (required)'),
|
|
3053
3810
|
notes: z.string().max(2000).optional().describe('Notes or context for this measurement'),
|
|
3054
|
-
is_auto: z
|
|
3811
|
+
is_auto: z
|
|
3812
|
+
.boolean()
|
|
3813
|
+
.optional()
|
|
3814
|
+
.describe('Whether this is an auto-calculated value (default: false)'),
|
|
3055
3815
|
}, async (params) => {
|
|
3056
3816
|
try {
|
|
3057
3817
|
const result = await client.create('los-measurements', buildBody(params));
|
|
@@ -3065,7 +3825,10 @@ export function registerWriteTools(server, client) {
|
|
|
3065
3825
|
id: z.string().uuid().describe('LoS measurement ID'),
|
|
3066
3826
|
actual_value: z.number().optional().describe('Measured value'),
|
|
3067
3827
|
notes: z.string().max(2000).optional().describe('Notes or context'),
|
|
3068
|
-
period_type: z
|
|
3828
|
+
period_type: z
|
|
3829
|
+
.enum(['monthly', 'quarterly', 'semi_annual', 'annual'])
|
|
3830
|
+
.optional()
|
|
3831
|
+
.describe('Period type'),
|
|
3069
3832
|
period_start: z.string().optional().describe('Period start date (ISO 8601)'),
|
|
3070
3833
|
period_end: z.string().optional().describe('Period end date (ISO 8601)'),
|
|
3071
3834
|
is_auto: z.boolean().optional().describe('Whether this is auto-calculated'),
|
|
@@ -3097,14 +3860,39 @@ export function registerWriteTools(server, client) {
|
|
|
3097
3860
|
.min(3)
|
|
3098
3861
|
.describe('Polygon outline as an array of [x, y] points in normalized 0-1 coordinates (origin top-left). At least 3 points.');
|
|
3099
3862
|
server.tool('create_floorplan', 'Create a floorplan row. Provide EXACTLY ONE of building_id (per-building floor) or site_id (site-level / campus plan). Typically the web app calls this per page of an uploaded PDF; MCP clients rarely need to call this directly since they do not upload the PDF itself. Requires floorplans:write scope.', {
|
|
3100
|
-
building_id: z
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3863
|
+
building_id: z
|
|
3864
|
+
.string()
|
|
3865
|
+
.uuid()
|
|
3866
|
+
.optional()
|
|
3867
|
+
.describe('Building this floor belongs to (omit if site-scoped)'),
|
|
3868
|
+
site_id: z
|
|
3869
|
+
.string()
|
|
3870
|
+
.uuid()
|
|
3871
|
+
.optional()
|
|
3872
|
+
.describe('Site this plan belongs to (use for site-level / campus plans; omit if building-scoped)'),
|
|
3873
|
+
floor_label: z
|
|
3874
|
+
.string()
|
|
3875
|
+
.max(200)
|
|
3876
|
+
.describe('Human-readable label (e.g. "Ground Floor", "Mezzanine", "Site Plan")'),
|
|
3877
|
+
floor_order: z
|
|
3878
|
+
.number()
|
|
3879
|
+
.int()
|
|
3880
|
+
.min(0)
|
|
3881
|
+
.optional()
|
|
3882
|
+
.describe('Sort order within the scope (lowest first)'),
|
|
3104
3883
|
pdf_storage_path: z.string().max(1000).describe('Supabase Storage path to the PDF file'),
|
|
3105
3884
|
pdf_filename: z.string().max(500).describe('Original filename for display'),
|
|
3106
|
-
page_number: z
|
|
3107
|
-
|
|
3885
|
+
page_number: z
|
|
3886
|
+
.number()
|
|
3887
|
+
.int()
|
|
3888
|
+
.min(1)
|
|
3889
|
+
.optional()
|
|
3890
|
+
.describe('1-indexed page number within the PDF'),
|
|
3891
|
+
page_width_pt: z
|
|
3892
|
+
.number()
|
|
3893
|
+
.min(0)
|
|
3894
|
+
.optional()
|
|
3895
|
+
.describe('Page width in PDF points (discovered client-side)'),
|
|
3108
3896
|
page_height_pt: z.number().min(0).optional().describe('Page height in PDF points'),
|
|
3109
3897
|
status: z.enum(FLOORPLAN_STATUSES).optional().describe('Detection status (default: pending)'),
|
|
3110
3898
|
}, async (params) => {
|
|
@@ -3149,10 +3937,25 @@ export function registerWriteTools(server, client) {
|
|
|
3149
3937
|
floorplan_id: z.string().uuid().describe('Floorplan this region belongs to'),
|
|
3150
3938
|
label: z.string().max(500).describe('Region label (e.g. "Boiler Room 2B")'),
|
|
3151
3939
|
polygon: polygonSchema,
|
|
3152
|
-
location_id: z
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3940
|
+
location_id: z
|
|
3941
|
+
.string()
|
|
3942
|
+
.uuid()
|
|
3943
|
+
.optional()
|
|
3944
|
+
.describe('Linked Location ID (resolved via list_locations)'),
|
|
3945
|
+
source: z
|
|
3946
|
+
.enum(REGION_SOURCES)
|
|
3947
|
+
.optional()
|
|
3948
|
+
.describe('"manual" (default) or "ai" for AI-detected'),
|
|
3949
|
+
confidence: z
|
|
3950
|
+
.number()
|
|
3951
|
+
.min(0)
|
|
3952
|
+
.max(1)
|
|
3953
|
+
.optional()
|
|
3954
|
+
.describe('AI confidence score (0-1), only set when source=ai'),
|
|
3955
|
+
reviewed: z
|
|
3956
|
+
.boolean()
|
|
3957
|
+
.optional()
|
|
3958
|
+
.describe('True if an admin has reviewed this region (default: true for manual, false for ai)'),
|
|
3156
3959
|
}, async (params) => {
|
|
3157
3960
|
try {
|
|
3158
3961
|
const result = await client.create('floorplan-regions', buildBody(params));
|
|
@@ -3166,7 +3969,11 @@ export function registerWriteTools(server, client) {
|
|
|
3166
3969
|
id: z.string().uuid().describe('Floorplan region ID'),
|
|
3167
3970
|
label: z.string().max(500).optional().describe('New label'),
|
|
3168
3971
|
polygon: polygonSchema.optional(),
|
|
3169
|
-
location_id: z
|
|
3972
|
+
location_id: z
|
|
3973
|
+
.string()
|
|
3974
|
+
.uuid()
|
|
3975
|
+
.optional()
|
|
3976
|
+
.describe('Linked Location ID (set to null to unlink)'),
|
|
3170
3977
|
confidence: z.number().min(0).max(1).optional(),
|
|
3171
3978
|
reviewed: z.boolean().optional().describe('Mark region as reviewed by admin'),
|
|
3172
3979
|
}, async ({ id, ...rest }) => {
|
|
@@ -3192,8 +3999,15 @@ export function registerWriteTools(server, client) {
|
|
|
3192
3999
|
floorplan_id: z.string().uuid().describe('Target floorplan (resolve via list_floorplans)'),
|
|
3193
4000
|
x: z.number().min(0).max(1).describe('Normalized x coordinate (0=left, 1=right)'),
|
|
3194
4001
|
y: z.number().min(0).max(1).describe('Normalized y coordinate (0=top, 1=bottom)'),
|
|
3195
|
-
region_id: z
|
|
3196
|
-
|
|
4002
|
+
region_id: z
|
|
4003
|
+
.string()
|
|
4004
|
+
.uuid()
|
|
4005
|
+
.optional()
|
|
4006
|
+
.describe('Optional region the pin sits inside (usually auto-inferred)'),
|
|
4007
|
+
source: z
|
|
4008
|
+
.enum(REGION_SOURCES)
|
|
4009
|
+
.optional()
|
|
4010
|
+
.describe('"manual" (default) or "ai" for AI-placed'),
|
|
3197
4011
|
}, async (params) => {
|
|
3198
4012
|
try {
|
|
3199
4013
|
const result = await client.create('asset-placements', buildBody(params));
|
|
@@ -3231,30 +4045,80 @@ export function registerWriteTools(server, client) {
|
|
|
3231
4045
|
// Bulk operations
|
|
3232
4046
|
// ============================================================
|
|
3233
4047
|
const BULK_RESOURCES = [
|
|
3234
|
-
'assets',
|
|
3235
|
-
'
|
|
3236
|
-
'
|
|
3237
|
-
'
|
|
3238
|
-
'
|
|
3239
|
-
'
|
|
3240
|
-
'
|
|
3241
|
-
'
|
|
3242
|
-
'
|
|
3243
|
-
'
|
|
3244
|
-
'
|
|
3245
|
-
'
|
|
3246
|
-
'
|
|
3247
|
-
'
|
|
3248
|
-
'
|
|
3249
|
-
'
|
|
3250
|
-
'
|
|
3251
|
-
'
|
|
3252
|
-
'
|
|
3253
|
-
'
|
|
4048
|
+
'assets',
|
|
4049
|
+
'work-orders',
|
|
4050
|
+
'work-requests',
|
|
4051
|
+
'vendors',
|
|
4052
|
+
'sites',
|
|
4053
|
+
'buildings',
|
|
4054
|
+
'locations',
|
|
4055
|
+
'systems',
|
|
4056
|
+
'system-groups',
|
|
4057
|
+
'system-classes',
|
|
4058
|
+
'pm-schedules',
|
|
4059
|
+
'pm-templates',
|
|
4060
|
+
'projects',
|
|
4061
|
+
'contracts',
|
|
4062
|
+
'invoices',
|
|
4063
|
+
'purchase-orders',
|
|
4064
|
+
'expenses',
|
|
4065
|
+
'budgets',
|
|
4066
|
+
'asset-types',
|
|
4067
|
+
'asset-type-groups',
|
|
4068
|
+
'asset-statuses',
|
|
4069
|
+
'work-categories',
|
|
4070
|
+
'manufacturers',
|
|
4071
|
+
'building-types',
|
|
4072
|
+
'location-types',
|
|
4073
|
+
'cost-categories',
|
|
4074
|
+
'compliance',
|
|
4075
|
+
'compliance-records',
|
|
4076
|
+
'asset-comments',
|
|
4077
|
+
'asset-costs',
|
|
4078
|
+
'asset-replacement-plans',
|
|
4079
|
+
'work-order-comments',
|
|
4080
|
+
'project-tasks',
|
|
4081
|
+
'project-milestones',
|
|
4082
|
+
'project-phases',
|
|
4083
|
+
'project-budget-items',
|
|
4084
|
+
'project-time-entries',
|
|
4085
|
+
'project-comments',
|
|
4086
|
+
'project-team-members',
|
|
4087
|
+
'project-task-dependencies',
|
|
4088
|
+
'project-updates',
|
|
4089
|
+
'project-cost-snapshots',
|
|
4090
|
+
'project-locations',
|
|
4091
|
+
'project-sites',
|
|
4092
|
+
'project-buildings',
|
|
4093
|
+
'project-systems',
|
|
4094
|
+
'project-system-classes',
|
|
4095
|
+
'project-system-groups',
|
|
4096
|
+
'project-assets',
|
|
4097
|
+
'project-risks',
|
|
4098
|
+
'parts',
|
|
4099
|
+
'part-categories',
|
|
4100
|
+
'custom-field-definitions',
|
|
4101
|
+
'custom-field-values',
|
|
4102
|
+
'vendor-site-assignments',
|
|
4103
|
+
'contract-sites',
|
|
4104
|
+
'asset-documents',
|
|
4105
|
+
'attachments',
|
|
4106
|
+
'project-documents',
|
|
4107
|
+
'contract-documents',
|
|
4108
|
+
'service-areas',
|
|
4109
|
+
'los-measures',
|
|
4110
|
+
'los-measurements',
|
|
4111
|
+
'floorplans',
|
|
4112
|
+
'floorplan-regions',
|
|
4113
|
+
'asset-placements',
|
|
3254
4114
|
];
|
|
3255
4115
|
server.tool('bulk_create', 'Create multiple records of a resource type in one API call (max 100). Each item is processed independently — one failure does not affect others. Returns per-item results. Requires {resource}:write scope. Counts as 1 request for rate limiting.', {
|
|
3256
4116
|
resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
|
|
3257
|
-
items: z
|
|
4117
|
+
items: z
|
|
4118
|
+
.array(z.record(z.unknown()))
|
|
4119
|
+
.min(1)
|
|
4120
|
+
.max(100)
|
|
4121
|
+
.describe('Array of objects to create (max 100). Each object uses the same fields as the single-create endpoint for that resource.'),
|
|
3258
4122
|
}, async ({ resource, items }) => {
|
|
3259
4123
|
try {
|
|
3260
4124
|
const result = await client.bulkCreate(resource, items);
|
|
@@ -3308,7 +4172,12 @@ export function registerWriteTools(server, client) {
|
|
|
3308
4172
|
name: z.string().max(500).describe('Compliance item name (required)'),
|
|
3309
4173
|
description: z.string().optional().describe('Description'),
|
|
3310
4174
|
regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
|
|
3311
|
-
compliance_period_months: z
|
|
4175
|
+
compliance_period_months: z
|
|
4176
|
+
.number()
|
|
4177
|
+
.int()
|
|
4178
|
+
.min(1)
|
|
4179
|
+
.optional()
|
|
4180
|
+
.describe('Compliance period in months'),
|
|
3312
4181
|
status: z.enum(['active', 'archived']).optional().describe('Status'),
|
|
3313
4182
|
system_id: z.string().uuid().optional().describe('Associated system ID'),
|
|
3314
4183
|
}, async (params) => {
|
|
@@ -3325,7 +4194,12 @@ export function registerWriteTools(server, client) {
|
|
|
3325
4194
|
name: z.string().max(500).optional().describe('Compliance item name'),
|
|
3326
4195
|
description: z.string().optional().describe('Description'),
|
|
3327
4196
|
regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
|
|
3328
|
-
compliance_period_months: z
|
|
4197
|
+
compliance_period_months: z
|
|
4198
|
+
.number()
|
|
4199
|
+
.int()
|
|
4200
|
+
.min(1)
|
|
4201
|
+
.optional()
|
|
4202
|
+
.describe('Compliance period in months'),
|
|
3329
4203
|
status: z.enum(['active', 'archived']).optional().describe('Status'),
|
|
3330
4204
|
system_id: z.string().uuid().optional().describe('Associated system ID'),
|
|
3331
4205
|
}, async ({ id, ...rest }) => {
|
|
@@ -3355,8 +4229,17 @@ export function registerWriteTools(server, client) {
|
|
|
3355
4229
|
work_order_id: z.string().uuid().describe('Work order ID (required)'),
|
|
3356
4230
|
completed_at: z.string().describe('Completion date-time (ISO 8601, required)'),
|
|
3357
4231
|
completed_by: z.string().max(200).optional().describe('User ID who completed'),
|
|
3358
|
-
required_frequency_days: z
|
|
3359
|
-
|
|
4232
|
+
required_frequency_days: z
|
|
4233
|
+
.number()
|
|
4234
|
+
.int()
|
|
4235
|
+
.min(1)
|
|
4236
|
+
.describe('Required frequency in days (required)'),
|
|
4237
|
+
days_since_last_completion: z
|
|
4238
|
+
.number()
|
|
4239
|
+
.int()
|
|
4240
|
+
.min(0)
|
|
4241
|
+
.optional()
|
|
4242
|
+
.describe('Days since last completion'),
|
|
3360
4243
|
}, async (params) => {
|
|
3361
4244
|
try {
|
|
3362
4245
|
const result = await client.create('compliance-records', buildBody(params));
|
|
@@ -3370,8 +4253,18 @@ export function registerWriteTools(server, client) {
|
|
|
3370
4253
|
id: z.string().uuid().describe('Compliance record ID'),
|
|
3371
4254
|
completed_at: z.string().optional().describe('Completion date-time (ISO 8601)'),
|
|
3372
4255
|
completed_by: z.string().max(200).optional().describe('User ID who completed'),
|
|
3373
|
-
required_frequency_days: z
|
|
3374
|
-
|
|
4256
|
+
required_frequency_days: z
|
|
4257
|
+
.number()
|
|
4258
|
+
.int()
|
|
4259
|
+
.min(1)
|
|
4260
|
+
.optional()
|
|
4261
|
+
.describe('Required frequency in days'),
|
|
4262
|
+
days_since_last_completion: z
|
|
4263
|
+
.number()
|
|
4264
|
+
.int()
|
|
4265
|
+
.min(0)
|
|
4266
|
+
.optional()
|
|
4267
|
+
.describe('Days since last completion'),
|
|
3375
4268
|
}, async ({ id, ...rest }) => {
|
|
3376
4269
|
try {
|
|
3377
4270
|
const result = await client.update('compliance-records', id, buildBody(rest));
|
|
@@ -3395,7 +4288,11 @@ export function registerWriteTools(server, client) {
|
|
|
3395
4288
|
// ============================================================
|
|
3396
4289
|
server.tool('bulk_update', 'Update multiple records of a resource type in one API call (max 100). Each item must include an "id" field (UUID). Each item is processed independently — one failure does not affect others. Returns per-item results. Requires {resource}:write scope. Counts as 1 request for rate limiting.', {
|
|
3397
4290
|
resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
|
|
3398
|
-
items: z
|
|
4291
|
+
items: z
|
|
4292
|
+
.array(z.record(z.unknown()))
|
|
4293
|
+
.min(1)
|
|
4294
|
+
.max(100)
|
|
4295
|
+
.describe('Array of objects to update (max 100). Each must include an "id" field (UUID) plus fields to change.'),
|
|
3399
4296
|
}, async ({ resource, items }) => {
|
|
3400
4297
|
try {
|
|
3401
4298
|
const result = await client.bulkUpdate(resource, items);
|