@assetlab/mcp-server 1.19.7 → 1.20.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 +53 -0
- package/dist/response-shaping.js.map +1 -0
- package/dist/tools-write.js +1148 -255
- package/dist/tools-write.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +383 -223
- 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));
|
|
@@ -1239,7 +1670,9 @@ export function registerWriteTools(server, client) {
|
|
|
1239
1670
|
// 18. Asset Costs (scope: asset_costs)
|
|
1240
1671
|
// ============================================================
|
|
1241
1672
|
server.tool('create_asset_cost', 'Create a new asset cost entry. Requires asset_costs:write scope.', {
|
|
1242
|
-
category: z
|
|
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'),
|
|
@@ -1258,7 +1691,10 @@ export function registerWriteTools(server, client) {
|
|
|
1258
1691
|
});
|
|
1259
1692
|
server.tool('update_asset_cost', 'Update an existing asset cost entry by ID. Requires asset_costs:write scope.', {
|
|
1260
1693
|
id: z.string().uuid().describe('Asset cost ID'),
|
|
1261
|
-
category: z
|
|
1694
|
+
category: z
|
|
1695
|
+
.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other'])
|
|
1696
|
+
.optional()
|
|
1697
|
+
.describe('Cost category'),
|
|
1262
1698
|
amount: z.number().min(0).optional().describe('Cost amount'),
|
|
1263
1699
|
cost_date: z.string().optional().describe('Cost date (ISO 8601)'),
|
|
1264
1700
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
@@ -1289,10 +1725,18 @@ export function registerWriteTools(server, client) {
|
|
|
1289
1725
|
// ============================================================
|
|
1290
1726
|
server.tool('create_asset_replacement_plan', 'Create a new asset replacement plan. Requires asset_replacement_plans:write scope.', {
|
|
1291
1727
|
asset_id: z.string().uuid().describe('Asset ID (required)'),
|
|
1292
|
-
planned_replacement_year: z
|
|
1728
|
+
planned_replacement_year: z
|
|
1729
|
+
.number()
|
|
1730
|
+
.int()
|
|
1731
|
+
.min(2000)
|
|
1732
|
+
.max(2100)
|
|
1733
|
+
.describe('Planned replacement year (required)'),
|
|
1293
1734
|
estimated_cost: z.number().min(0).optional().describe('Estimated replacement cost'),
|
|
1294
1735
|
priority: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).optional().describe('Priority'),
|
|
1295
|
-
status: z
|
|
1736
|
+
status: z
|
|
1737
|
+
.enum(['PLANNED', 'BUDGETED', 'APPROVED', 'COMPLETED', 'CANCELLED'])
|
|
1738
|
+
.optional()
|
|
1739
|
+
.describe('Status'),
|
|
1296
1740
|
notes: z.string().optional().describe('Notes'),
|
|
1297
1741
|
funding_source: z.string().max(200).optional().describe('Funding source'),
|
|
1298
1742
|
}, async (params) => {
|
|
@@ -1307,10 +1751,19 @@ export function registerWriteTools(server, client) {
|
|
|
1307
1751
|
server.tool('update_asset_replacement_plan', 'Update an existing asset replacement plan by ID. Requires asset_replacement_plans:write scope.', {
|
|
1308
1752
|
id: z.string().uuid().describe('Asset replacement plan ID'),
|
|
1309
1753
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
1310
|
-
planned_replacement_year: z
|
|
1754
|
+
planned_replacement_year: z
|
|
1755
|
+
.number()
|
|
1756
|
+
.int()
|
|
1757
|
+
.min(2000)
|
|
1758
|
+
.max(2100)
|
|
1759
|
+
.optional()
|
|
1760
|
+
.describe('Planned replacement year'),
|
|
1311
1761
|
estimated_cost: z.number().min(0).optional().describe('Estimated replacement cost'),
|
|
1312
1762
|
priority: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).optional().describe('Priority'),
|
|
1313
|
-
status: z
|
|
1763
|
+
status: z
|
|
1764
|
+
.enum(['PLANNED', 'BUDGETED', 'APPROVED', 'COMPLETED', 'CANCELLED'])
|
|
1765
|
+
.optional()
|
|
1766
|
+
.describe('Status'),
|
|
1314
1767
|
notes: z.string().optional().describe('Notes'),
|
|
1315
1768
|
funding_source: z.string().max(200).optional().describe('Funding source'),
|
|
1316
1769
|
}, async ({ id, ...rest }) => {
|
|
@@ -1339,7 +1792,10 @@ export function registerWriteTools(server, client) {
|
|
|
1339
1792
|
title: z.string().min(1).max(500).describe('Task title (required)'),
|
|
1340
1793
|
description: z.string().optional().describe('Description'),
|
|
1341
1794
|
phase_id: z.string().uuid().optional().describe('Phase ID'),
|
|
1342
|
-
status: z
|
|
1795
|
+
status: z
|
|
1796
|
+
.enum(['todo', 'in_progress', 'completed', 'blocked', 'cancelled'])
|
|
1797
|
+
.optional()
|
|
1798
|
+
.describe('Task status'),
|
|
1343
1799
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Priority'),
|
|
1344
1800
|
assigned_to: z.string().max(200).optional().describe('Assigned user'),
|
|
1345
1801
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
@@ -1361,7 +1817,10 @@ export function registerWriteTools(server, client) {
|
|
|
1361
1817
|
title: z.string().min(1).max(500).optional().describe('Task title'),
|
|
1362
1818
|
description: z.string().optional().describe('Description'),
|
|
1363
1819
|
phase_id: z.string().uuid().optional().describe('Phase ID'),
|
|
1364
|
-
status: z
|
|
1820
|
+
status: z
|
|
1821
|
+
.enum(['todo', 'in_progress', 'completed', 'blocked', 'cancelled'])
|
|
1822
|
+
.optional()
|
|
1823
|
+
.describe('Task status'),
|
|
1365
1824
|
priority: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Priority'),
|
|
1366
1825
|
assigned_to: z.string().max(200).optional().describe('Assigned user'),
|
|
1367
1826
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
@@ -1394,7 +1853,10 @@ export function registerWriteTools(server, client) {
|
|
|
1394
1853
|
name: z.string().min(1).max(500).describe('Milestone name (required)'),
|
|
1395
1854
|
due_date: z.string().describe('Due date (ISO 8601, required)'),
|
|
1396
1855
|
description: z.string().optional().describe('Description'),
|
|
1397
|
-
status: z
|
|
1856
|
+
status: z
|
|
1857
|
+
.enum(['pending', 'completed', 'missed', 'at_risk'])
|
|
1858
|
+
.optional()
|
|
1859
|
+
.describe('Milestone status'),
|
|
1398
1860
|
completed_date: z.string().optional().describe('Completed date (ISO 8601)'),
|
|
1399
1861
|
}, async (params) => {
|
|
1400
1862
|
try {
|
|
@@ -1411,7 +1873,10 @@ export function registerWriteTools(server, client) {
|
|
|
1411
1873
|
name: z.string().min(1).max(500).optional().describe('Milestone name'),
|
|
1412
1874
|
due_date: z.string().optional().describe('Due date (ISO 8601)'),
|
|
1413
1875
|
description: z.string().optional().describe('Description'),
|
|
1414
|
-
status: z
|
|
1876
|
+
status: z
|
|
1877
|
+
.enum(['pending', 'completed', 'missed', 'at_risk'])
|
|
1878
|
+
.optional()
|
|
1879
|
+
.describe('Milestone status'),
|
|
1415
1880
|
completed_date: z.string().optional().describe('Completed date (ISO 8601)'),
|
|
1416
1881
|
}, async ({ id, ...rest }) => {
|
|
1417
1882
|
try {
|
|
@@ -1438,7 +1903,10 @@ export function registerWriteTools(server, client) {
|
|
|
1438
1903
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
1439
1904
|
name: z.string().min(1).max(500).describe('Phase name (required)'),
|
|
1440
1905
|
description: z.string().optional().describe('Description'),
|
|
1441
|
-
status: z
|
|
1906
|
+
status: z
|
|
1907
|
+
.enum(['pending', 'in_progress', 'completed', 'skipped'])
|
|
1908
|
+
.optional()
|
|
1909
|
+
.describe('Phase status'),
|
|
1442
1910
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
1443
1911
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
1444
1912
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
@@ -1456,7 +1924,10 @@ export function registerWriteTools(server, client) {
|
|
|
1456
1924
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
1457
1925
|
name: z.string().min(1).max(500).optional().describe('Phase name'),
|
|
1458
1926
|
description: z.string().optional().describe('Description'),
|
|
1459
|
-
status: z
|
|
1927
|
+
status: z
|
|
1928
|
+
.enum(['pending', 'in_progress', 'completed', 'skipped'])
|
|
1929
|
+
.optional()
|
|
1930
|
+
.describe('Phase status'),
|
|
1460
1931
|
start_date: z.string().optional().describe('Start date (ISO 8601)'),
|
|
1461
1932
|
end_date: z.string().optional().describe('End date (ISO 8601)'),
|
|
1462
1933
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
@@ -1483,7 +1954,17 @@ export function registerWriteTools(server, client) {
|
|
|
1483
1954
|
// ============================================================
|
|
1484
1955
|
server.tool('create_project_budget_item', 'Create a new project budget item. Requires project_budget_items:write scope.', {
|
|
1485
1956
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
1486
|
-
category: z
|
|
1957
|
+
category: z
|
|
1958
|
+
.enum([
|
|
1959
|
+
'labor',
|
|
1960
|
+
'materials',
|
|
1961
|
+
'equipment',
|
|
1962
|
+
'subcontractors',
|
|
1963
|
+
'permits',
|
|
1964
|
+
'contingency',
|
|
1965
|
+
'other',
|
|
1966
|
+
])
|
|
1967
|
+
.describe('Budget category (required)'),
|
|
1487
1968
|
description: z.string().optional().describe('Description'),
|
|
1488
1969
|
estimated_amount: z.number().min(0).optional().describe('Estimated amount'),
|
|
1489
1970
|
actual_amount: z.number().min(0).optional().describe('Actual amount'),
|
|
@@ -1499,7 +1980,18 @@ export function registerWriteTools(server, client) {
|
|
|
1499
1980
|
server.tool('update_project_budget_item', 'Update an existing project budget item by ID. Requires project_budget_items:write scope.', {
|
|
1500
1981
|
id: z.string().uuid().describe('Project budget item ID'),
|
|
1501
1982
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
1502
|
-
category: z
|
|
1983
|
+
category: z
|
|
1984
|
+
.enum([
|
|
1985
|
+
'labor',
|
|
1986
|
+
'materials',
|
|
1987
|
+
'equipment',
|
|
1988
|
+
'subcontractors',
|
|
1989
|
+
'permits',
|
|
1990
|
+
'contingency',
|
|
1991
|
+
'other',
|
|
1992
|
+
])
|
|
1993
|
+
.optional()
|
|
1994
|
+
.describe('Budget category'),
|
|
1503
1995
|
description: z.string().optional().describe('Description'),
|
|
1504
1996
|
estimated_amount: z.number().min(0).optional().describe('Estimated amount'),
|
|
1505
1997
|
actual_amount: z.number().min(0).optional().describe('Actual amount'),
|
|
@@ -1810,7 +2302,13 @@ export function registerWriteTools(server, client) {
|
|
|
1810
2302
|
server.tool('create_project_phase_category', 'Create a new project phase category. Requires project_phase_categories:write scope.', {
|
|
1811
2303
|
name: z.string().min(1).max(500).describe('Phase name (required), e.g. "Planning", "Design"'),
|
|
1812
2304
|
description: z.string().max(2000).optional().describe('Description of the phase'),
|
|
1813
|
-
sort_order: z
|
|
2305
|
+
sort_order: z
|
|
2306
|
+
.number()
|
|
2307
|
+
.int()
|
|
2308
|
+
.min(0)
|
|
2309
|
+
.max(10000)
|
|
2310
|
+
.optional()
|
|
2311
|
+
.describe('Display order (lower = first)'),
|
|
1814
2312
|
}, async (params) => {
|
|
1815
2313
|
try {
|
|
1816
2314
|
const result = await client.create('project-phase-categories', buildBody(params));
|
|
@@ -1824,7 +2322,13 @@ export function registerWriteTools(server, client) {
|
|
|
1824
2322
|
id: z.string().uuid().describe('Project phase category ID'),
|
|
1825
2323
|
name: z.string().min(1).max(500).optional().describe('Phase name'),
|
|
1826
2324
|
description: z.string().max(2000).optional().describe('Description'),
|
|
1827
|
-
sort_order: z
|
|
2325
|
+
sort_order: z
|
|
2326
|
+
.number()
|
|
2327
|
+
.int()
|
|
2328
|
+
.min(0)
|
|
2329
|
+
.max(10000)
|
|
2330
|
+
.optional()
|
|
2331
|
+
.describe('Display order (lower = first)'),
|
|
1828
2332
|
}, async ({ id, ...rest }) => {
|
|
1829
2333
|
try {
|
|
1830
2334
|
const result = await client.update('project-phase-categories', id, buildBody(rest));
|
|
@@ -2129,9 +2633,15 @@ export function registerWriteTools(server, client) {
|
|
|
2129
2633
|
// 35. Custom Field Definitions (scope: custom_fields)
|
|
2130
2634
|
// ============================================================
|
|
2131
2635
|
server.tool('create_custom_field_definition', 'Create a new custom field definition. Requires custom_fields:write scope.', {
|
|
2132
|
-
entity_type: z
|
|
2636
|
+
entity_type: z
|
|
2637
|
+
.string()
|
|
2638
|
+
.min(1)
|
|
2639
|
+
.max(100)
|
|
2640
|
+
.describe('Entity type this field applies to (required)'),
|
|
2133
2641
|
field_name: z.string().min(1).max(200).describe('Field name / key (required)'),
|
|
2134
|
-
field_type: z
|
|
2642
|
+
field_type: z
|
|
2643
|
+
.enum(['text', 'number', 'date', 'boolean', 'select'])
|
|
2644
|
+
.describe('Field data type (required)'),
|
|
2135
2645
|
field_label: z.string().max(200).optional().describe('Display label'),
|
|
2136
2646
|
options: z.array(z.string()).optional().describe('Options for select-type fields'),
|
|
2137
2647
|
is_required: z.boolean().optional().describe('Whether the field is required'),
|
|
@@ -2149,7 +2659,10 @@ export function registerWriteTools(server, client) {
|
|
|
2149
2659
|
id: z.string().uuid().describe('Custom field definition ID'),
|
|
2150
2660
|
entity_type: z.string().min(1).max(100).optional().describe('Entity type'),
|
|
2151
2661
|
field_name: z.string().min(1).max(200).optional().describe('Field name / key'),
|
|
2152
|
-
field_type: z
|
|
2662
|
+
field_type: z
|
|
2663
|
+
.enum(['text', 'number', 'date', 'boolean', 'select'])
|
|
2664
|
+
.optional()
|
|
2665
|
+
.describe('Field data type'),
|
|
2153
2666
|
field_label: z.string().max(200).optional().describe('Display label'),
|
|
2154
2667
|
options: z.array(z.string()).optional().describe('Options for select-type fields'),
|
|
2155
2668
|
is_required: z.boolean().optional().describe('Whether the field is required'),
|
|
@@ -2175,14 +2688,38 @@ export function registerWriteTools(server, client) {
|
|
|
2175
2688
|
// ============================================================
|
|
2176
2689
|
// 36. Custom Field Values (scope: custom_fields)
|
|
2177
2690
|
// ============================================================
|
|
2178
|
-
server.tool('create_custom_field_value',
|
|
2691
|
+
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
2692
|
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
|
-
|
|
2693
|
+
field_definition_id: z
|
|
2694
|
+
.string()
|
|
2695
|
+
.uuid()
|
|
2696
|
+
.describe('Custom field definition ID — resolve via list_custom_field_definitions (required)'),
|
|
2697
|
+
value_text: z
|
|
2698
|
+
.string()
|
|
2699
|
+
.max(5000)
|
|
2700
|
+
.nullable()
|
|
2701
|
+
.optional()
|
|
2702
|
+
.describe('Text value (use for field_type=text or select)'),
|
|
2703
|
+
value_number: z
|
|
2704
|
+
.number()
|
|
2705
|
+
.nullable()
|
|
2706
|
+
.optional()
|
|
2707
|
+
.describe('Numeric value (use for field_type=number)'),
|
|
2708
|
+
value_date: z
|
|
2709
|
+
.string()
|
|
2710
|
+
.nullable()
|
|
2711
|
+
.optional()
|
|
2712
|
+
.describe('Date value, ISO YYYY-MM-DD (use for field_type=date)'),
|
|
2713
|
+
value_boolean: z
|
|
2714
|
+
.boolean()
|
|
2715
|
+
.nullable()
|
|
2716
|
+
.optional()
|
|
2717
|
+
.describe('Boolean value (use for field_type=boolean)'),
|
|
2718
|
+
value: z
|
|
2719
|
+
.string()
|
|
2720
|
+
.max(5000)
|
|
2721
|
+
.optional()
|
|
2722
|
+
.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
2723
|
}, async (params) => {
|
|
2187
2724
|
try {
|
|
2188
2725
|
const result = await client.create('custom-field-values', buildBody(params));
|
|
@@ -2192,15 +2729,23 @@ export function registerWriteTools(server, client) {
|
|
|
2192
2729
|
return formatError(err);
|
|
2193
2730
|
}
|
|
2194
2731
|
});
|
|
2195
|
-
server.tool('update_custom_field_value',
|
|
2732
|
+
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
2733
|
id: z.string().uuid().describe('Custom field value ID'),
|
|
2197
2734
|
entity_id: z.string().uuid().optional().describe('Entity ID'),
|
|
2198
|
-
field_definition_id: z
|
|
2735
|
+
field_definition_id: z
|
|
2736
|
+
.string()
|
|
2737
|
+
.uuid()
|
|
2738
|
+
.optional()
|
|
2739
|
+
.describe('Custom field definition ID — required when using the `value` fallback if you want to avoid the server fetching it'),
|
|
2199
2740
|
value_text: z.string().max(5000).nullable().optional().describe('Text value'),
|
|
2200
2741
|
value_number: z.number().nullable().optional().describe('Numeric value'),
|
|
2201
2742
|
value_date: z.string().nullable().optional().describe('Date value, ISO YYYY-MM-DD'),
|
|
2202
2743
|
value_boolean: z.boolean().nullable().optional().describe('Boolean value'),
|
|
2203
|
-
value: z
|
|
2744
|
+
value: z
|
|
2745
|
+
.string()
|
|
2746
|
+
.max(5000)
|
|
2747
|
+
.optional()
|
|
2748
|
+
.describe('Legacy single-value shim — server dispatches based on field_type'),
|
|
2204
2749
|
}, async ({ id, ...rest }) => {
|
|
2205
2750
|
try {
|
|
2206
2751
|
const result = await client.update('custom-field-values', id, buildBody(rest));
|
|
@@ -2226,11 +2771,27 @@ export function registerWriteTools(server, client) {
|
|
|
2226
2771
|
name: z.string().min(1).max(500).describe('Part name (required)'),
|
|
2227
2772
|
part_number: z.string().max(200).optional().describe('Part number / SKU'),
|
|
2228
2773
|
category: z.string().max(200).optional().describe('Category label'),
|
|
2229
|
-
supplier: z
|
|
2774
|
+
supplier: z
|
|
2775
|
+
.string()
|
|
2776
|
+
.max(500)
|
|
2777
|
+
.optional()
|
|
2778
|
+
.describe('DEPRECATED — legacy free-text supplier name. Use supplier_id instead.'),
|
|
2230
2779
|
supplier_id: z.string().uuid().optional().describe('Vendor ID (resolve via list_vendors)'),
|
|
2231
2780
|
cost: z.number().min(0).optional().describe('Unit cost'),
|
|
2232
|
-
quantity: z
|
|
2233
|
-
|
|
2781
|
+
quantity: z
|
|
2782
|
+
.number()
|
|
2783
|
+
.int()
|
|
2784
|
+
.min(0)
|
|
2785
|
+
.max(10_000_000)
|
|
2786
|
+
.optional()
|
|
2787
|
+
.describe('Current stock quantity'),
|
|
2788
|
+
desired_quantity: z
|
|
2789
|
+
.number()
|
|
2790
|
+
.int()
|
|
2791
|
+
.min(0)
|
|
2792
|
+
.max(10_000_000)
|
|
2793
|
+
.optional()
|
|
2794
|
+
.describe('Target / reorder quantity'),
|
|
2234
2795
|
specific_location: z.string().max(500).optional().describe('Storage location description'),
|
|
2235
2796
|
site_id: z.string().uuid().optional().describe('Site ID'),
|
|
2236
2797
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
@@ -2249,11 +2810,27 @@ export function registerWriteTools(server, client) {
|
|
|
2249
2810
|
name: z.string().min(1).max(500).optional().describe('Part name'),
|
|
2250
2811
|
part_number: z.string().max(200).optional().describe('Part number / SKU'),
|
|
2251
2812
|
category: z.string().max(200).optional().describe('Category label'),
|
|
2252
|
-
supplier: z
|
|
2813
|
+
supplier: z
|
|
2814
|
+
.string()
|
|
2815
|
+
.max(500)
|
|
2816
|
+
.optional()
|
|
2817
|
+
.describe('DEPRECATED — legacy free-text supplier name. Use supplier_id instead.'),
|
|
2253
2818
|
supplier_id: z.string().uuid().optional().describe('Vendor ID (resolve via list_vendors)'),
|
|
2254
2819
|
cost: z.number().min(0).optional().describe('Unit cost'),
|
|
2255
|
-
quantity: z
|
|
2256
|
-
|
|
2820
|
+
quantity: z
|
|
2821
|
+
.number()
|
|
2822
|
+
.int()
|
|
2823
|
+
.min(0)
|
|
2824
|
+
.max(10_000_000)
|
|
2825
|
+
.optional()
|
|
2826
|
+
.describe('Current stock quantity'),
|
|
2827
|
+
desired_quantity: z
|
|
2828
|
+
.number()
|
|
2829
|
+
.int()
|
|
2830
|
+
.min(0)
|
|
2831
|
+
.max(10_000_000)
|
|
2832
|
+
.optional()
|
|
2833
|
+
.describe('Target / reorder quantity'),
|
|
2257
2834
|
specific_location: z.string().max(500).optional().describe('Storage location description'),
|
|
2258
2835
|
site_id: z.string().uuid().optional().describe('Site ID'),
|
|
2259
2836
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
@@ -2280,7 +2857,15 @@ export function registerWriteTools(server, client) {
|
|
|
2280
2857
|
// Upload URLs
|
|
2281
2858
|
// ============================================================
|
|
2282
2859
|
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
|
|
2860
|
+
bucket: z
|
|
2861
|
+
.enum([
|
|
2862
|
+
'documents',
|
|
2863
|
+
'attachments',
|
|
2864
|
+
'project-documents',
|
|
2865
|
+
'contract-documents',
|
|
2866
|
+
'asset-images',
|
|
2867
|
+
])
|
|
2868
|
+
.describe('Storage bucket (required). Use "asset-images" for asset photos.'),
|
|
2284
2869
|
file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
|
|
2285
2870
|
}, async (params) => {
|
|
2286
2871
|
try {
|
|
@@ -2292,10 +2877,25 @@ export function registerWriteTools(server, client) {
|
|
|
2292
2877
|
}
|
|
2293
2878
|
});
|
|
2294
2879
|
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
|
|
2880
|
+
bucket: z
|
|
2881
|
+
.enum([
|
|
2882
|
+
'documents',
|
|
2883
|
+
'attachments',
|
|
2884
|
+
'project-documents',
|
|
2885
|
+
'contract-documents',
|
|
2886
|
+
'asset-images',
|
|
2887
|
+
])
|
|
2888
|
+
.describe('Storage bucket (required). Use "asset-images" for asset photos, "attachments" for work-order/PM attachments.'),
|
|
2296
2889
|
file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
|
|
2297
|
-
content_base64: z
|
|
2298
|
-
|
|
2890
|
+
content_base64: z
|
|
2891
|
+
.string()
|
|
2892
|
+
.min(1)
|
|
2893
|
+
.describe('File contents base64-encoded (required). Data URI prefixes like "data:image/png;base64," are stripped automatically.'),
|
|
2894
|
+
content_type: z
|
|
2895
|
+
.string()
|
|
2896
|
+
.max(200)
|
|
2897
|
+
.optional()
|
|
2898
|
+
.describe('MIME type (e.g. image/jpeg, application/pdf). Defaults to application/octet-stream.'),
|
|
2299
2899
|
}, async (params) => {
|
|
2300
2900
|
try {
|
|
2301
2901
|
const result = await client.create('upload-files', buildBody(params));
|
|
@@ -2310,9 +2910,16 @@ export function registerWriteTools(server, client) {
|
|
|
2310
2910
|
// ============================================================
|
|
2311
2911
|
server.tool('create_asset_document', 'Create an asset document record (after uploading the file via create_upload_url). Requires asset_documents:write scope.', {
|
|
2312
2912
|
name: z.string().min(1).max(500).describe('Document name (required)'),
|
|
2313
|
-
file_path: z
|
|
2913
|
+
file_path: z
|
|
2914
|
+
.string()
|
|
2915
|
+
.min(1)
|
|
2916
|
+
.max(2000)
|
|
2917
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2314
2918
|
asset_id: z.string().uuid().describe('Asset ID this document belongs to (required)'),
|
|
2315
|
-
category: z
|
|
2919
|
+
category: z
|
|
2920
|
+
.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other'])
|
|
2921
|
+
.optional()
|
|
2922
|
+
.describe('Document category'),
|
|
2316
2923
|
description: z.string().optional().describe('Description'),
|
|
2317
2924
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2318
2925
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
@@ -2331,7 +2938,10 @@ export function registerWriteTools(server, client) {
|
|
|
2331
2938
|
name: z.string().min(1).max(500).optional().describe('Document name'),
|
|
2332
2939
|
file_path: z.string().max(2000).optional().describe('Storage path'),
|
|
2333
2940
|
asset_id: z.string().uuid().optional().describe('Asset ID'),
|
|
2334
|
-
category: z
|
|
2941
|
+
category: z
|
|
2942
|
+
.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other'])
|
|
2943
|
+
.optional()
|
|
2944
|
+
.describe('Document category'),
|
|
2335
2945
|
description: z.string().optional().describe('Description'),
|
|
2336
2946
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2337
2947
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
@@ -2364,10 +2974,26 @@ export function registerWriteTools(server, client) {
|
|
|
2364
2974
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2365
2975
|
uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
|
|
2366
2976
|
description: z.string().optional().describe('Description'),
|
|
2367
|
-
work_order_id: z
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2977
|
+
work_order_id: z
|
|
2978
|
+
.string()
|
|
2979
|
+
.uuid()
|
|
2980
|
+
.optional()
|
|
2981
|
+
.describe('Work order ID (exactly one parent required)'),
|
|
2982
|
+
work_request_id: z
|
|
2983
|
+
.string()
|
|
2984
|
+
.uuid()
|
|
2985
|
+
.optional()
|
|
2986
|
+
.describe('Work request ID (exactly one parent required)'),
|
|
2987
|
+
pm_schedule_id: z
|
|
2988
|
+
.string()
|
|
2989
|
+
.uuid()
|
|
2990
|
+
.optional()
|
|
2991
|
+
.describe('PM schedule ID (exactly one parent required)'),
|
|
2992
|
+
pm_template_id: z
|
|
2993
|
+
.string()
|
|
2994
|
+
.uuid()
|
|
2995
|
+
.optional()
|
|
2996
|
+
.describe('PM template ID (exactly one parent required)'),
|
|
2371
2997
|
}, async (params) => {
|
|
2372
2998
|
try {
|
|
2373
2999
|
const result = await client.create('attachments', buildBody(params));
|
|
@@ -2413,7 +3039,11 @@ export function registerWriteTools(server, client) {
|
|
|
2413
3039
|
server.tool('create_project_document', 'Create a project document record. Requires project_documents:write scope.', {
|
|
2414
3040
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2415
3041
|
name: z.string().min(1).max(500).describe('Document name (required)'),
|
|
2416
|
-
file_path: z
|
|
3042
|
+
file_path: z
|
|
3043
|
+
.string()
|
|
3044
|
+
.min(1)
|
|
3045
|
+
.max(2000)
|
|
3046
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2417
3047
|
uploaded_by: z.string().min(1).max(200).describe('Uploader user ID (required)'),
|
|
2418
3048
|
folder_id: z.string().uuid().optional().describe('Folder ID'),
|
|
2419
3049
|
description: z.string().optional().describe('Description'),
|
|
@@ -2462,7 +3092,11 @@ export function registerWriteTools(server, client) {
|
|
|
2462
3092
|
server.tool('create_contract_document', 'Create a contract document record. Requires contract_documents:write scope.', {
|
|
2463
3093
|
contract_id: z.string().uuid().describe('Contract ID (required)'),
|
|
2464
3094
|
file_name: z.string().min(1).max(500).describe('File name (required)'),
|
|
2465
|
-
file_path: z
|
|
3095
|
+
file_path: z
|
|
3096
|
+
.string()
|
|
3097
|
+
.min(1)
|
|
3098
|
+
.max(2000)
|
|
3099
|
+
.describe('Storage path from upload URL response (required)'),
|
|
2466
3100
|
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
2467
3101
|
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
2468
3102
|
uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
|
|
@@ -2554,7 +3188,10 @@ export function registerWriteTools(server, client) {
|
|
|
2554
3188
|
server.tool('create_project_task_dependency', 'Create a dependency between two project tasks. Requires project_task_dependencies:write scope.', {
|
|
2555
3189
|
task_id: z.string().uuid().describe('Task ID (the dependent task, required)'),
|
|
2556
3190
|
depends_on_task_id: z.string().uuid().describe('Task ID that must complete first (required)'),
|
|
2557
|
-
dependency_type: z
|
|
3191
|
+
dependency_type: z
|
|
3192
|
+
.enum(['finish_to_start', 'start_to_start', 'finish_to_finish', 'start_to_finish'])
|
|
3193
|
+
.optional()
|
|
3194
|
+
.describe('Dependency type (default: finish_to_start)'),
|
|
2558
3195
|
}, async (params) => {
|
|
2559
3196
|
try {
|
|
2560
3197
|
const result = await client.create('project-task-dependencies', buildBody(params));
|
|
@@ -2579,9 +3216,20 @@ export function registerWriteTools(server, client) {
|
|
|
2579
3216
|
server.tool('create_project_update', 'Create a periodic project status update. Requires project_updates:write scope.', {
|
|
2580
3217
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2581
3218
|
author_id: z.string().min(1).max(200).describe('Author Clerk user ID (required)'),
|
|
2582
|
-
timeframe: z
|
|
2583
|
-
|
|
2584
|
-
|
|
3219
|
+
timeframe: z
|
|
3220
|
+
.enum(['monthly', 'quarterly', 'bi-annually', 'annually'])
|
|
3221
|
+
.describe('Update timeframe (required)'),
|
|
3222
|
+
period_year: z
|
|
3223
|
+
.number()
|
|
3224
|
+
.int()
|
|
3225
|
+
.min(2000)
|
|
3226
|
+
.max(2100)
|
|
3227
|
+
.describe('Year for this update period (required)'),
|
|
3228
|
+
period_value: z
|
|
3229
|
+
.string()
|
|
3230
|
+
.min(1)
|
|
3231
|
+
.max(20)
|
|
3232
|
+
.describe('Period value — 1-12 for monthly, 1-4 for quarterly, etc. (required)'),
|
|
2585
3233
|
content: z.string().min(1).describe('Update content (required)'),
|
|
2586
3234
|
title: z.string().max(500).optional().describe('Optional custom title'),
|
|
2587
3235
|
}, async (params) => {
|
|
@@ -2597,8 +3245,17 @@ export function registerWriteTools(server, client) {
|
|
|
2597
3245
|
id: z.string().uuid().describe('Project update ID'),
|
|
2598
3246
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
2599
3247
|
author_id: z.string().min(1).max(200).optional().describe('Author Clerk user ID'),
|
|
2600
|
-
timeframe: z
|
|
2601
|
-
|
|
3248
|
+
timeframe: z
|
|
3249
|
+
.enum(['monthly', 'quarterly', 'bi-annually', 'annually'])
|
|
3250
|
+
.optional()
|
|
3251
|
+
.describe('Update timeframe'),
|
|
3252
|
+
period_year: z
|
|
3253
|
+
.number()
|
|
3254
|
+
.int()
|
|
3255
|
+
.min(2000)
|
|
3256
|
+
.max(2100)
|
|
3257
|
+
.optional()
|
|
3258
|
+
.describe('Year for this update period'),
|
|
2602
3259
|
period_value: z.string().min(1).max(20).optional().describe('Period value'),
|
|
2603
3260
|
content: z.string().min(1).optional().describe('Update content'),
|
|
2604
3261
|
title: z.string().max(500).optional().describe('Optional custom title'),
|
|
@@ -2629,7 +3286,12 @@ export function registerWriteTools(server, client) {
|
|
|
2629
3286
|
total_budget: z.number().min(0).describe('Total budget amount (required)'),
|
|
2630
3287
|
actual_cost: z.number().min(0).describe('Actual cost to date (required)'),
|
|
2631
3288
|
forecasted_cost: z.number().min(0).optional().describe('Forecasted total cost'),
|
|
2632
|
-
percent_complete: z
|
|
3289
|
+
percent_complete: z
|
|
3290
|
+
.number()
|
|
3291
|
+
.min(0)
|
|
3292
|
+
.max(100)
|
|
3293
|
+
.optional()
|
|
3294
|
+
.describe('Completion percentage (0-100)'),
|
|
2633
3295
|
}, async (params) => {
|
|
2634
3296
|
try {
|
|
2635
3297
|
const result = await client.create('project-cost-snapshots', buildBody(params));
|
|
@@ -2799,10 +3461,16 @@ export function registerWriteTools(server, client) {
|
|
|
2799
3461
|
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
2800
3462
|
title: z.string().min(1).max(500).describe('Risk title (required)'),
|
|
2801
3463
|
description: z.string().optional().describe('Risk description'),
|
|
2802
|
-
category: z
|
|
3464
|
+
category: z
|
|
3465
|
+
.enum(['technical', 'financial', 'schedule', 'resource', 'external'])
|
|
3466
|
+
.optional()
|
|
3467
|
+
.describe('Risk category'),
|
|
2803
3468
|
probability: z.enum(['low', 'medium', 'high']).optional().describe('Probability level'),
|
|
2804
3469
|
impact: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Impact level'),
|
|
2805
|
-
status: z
|
|
3470
|
+
status: z
|
|
3471
|
+
.enum(['identified', 'analyzing', 'mitigating', 'resolved', 'accepted'])
|
|
3472
|
+
.optional()
|
|
3473
|
+
.describe('Risk status (default: identified)'),
|
|
2806
3474
|
mitigation_plan: z.string().optional().describe('Mitigation plan'),
|
|
2807
3475
|
contingency_plan: z.string().optional().describe('Contingency plan'),
|
|
2808
3476
|
owner_id: z.string().max(200).optional().describe('Risk owner (Clerk user ID)'),
|
|
@@ -2822,10 +3490,16 @@ export function registerWriteTools(server, client) {
|
|
|
2822
3490
|
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
2823
3491
|
title: z.string().min(1).max(500).optional().describe('Risk title'),
|
|
2824
3492
|
description: z.string().optional().describe('Risk description'),
|
|
2825
|
-
category: z
|
|
3493
|
+
category: z
|
|
3494
|
+
.enum(['technical', 'financial', 'schedule', 'resource', 'external'])
|
|
3495
|
+
.optional()
|
|
3496
|
+
.describe('Risk category'),
|
|
2826
3497
|
probability: z.enum(['low', 'medium', 'high']).optional().describe('Probability level'),
|
|
2827
3498
|
impact: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Impact level'),
|
|
2828
|
-
status: z
|
|
3499
|
+
status: z
|
|
3500
|
+
.enum(['identified', 'analyzing', 'mitigating', 'resolved', 'accepted'])
|
|
3501
|
+
.optional()
|
|
3502
|
+
.describe('Risk status'),
|
|
2829
3503
|
mitigation_plan: z.string().optional().describe('Mitigation plan'),
|
|
2830
3504
|
contingency_plan: z.string().optional().describe('Contingency plan'),
|
|
2831
3505
|
owner_id: z.string().max(200).optional().describe('Risk owner (Clerk user ID)'),
|
|
@@ -2881,7 +3555,10 @@ export function registerWriteTools(server, client) {
|
|
|
2881
3555
|
icon: z.string().max(200).optional().describe('Icon name (e.g., "droplets" for water)'),
|
|
2882
3556
|
color: z.string().max(50).optional().describe('Hex color code (e.g., "#3B82F6")'),
|
|
2883
3557
|
sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
|
|
2884
|
-
is_active: z
|
|
3558
|
+
is_active: z
|
|
3559
|
+
.boolean()
|
|
3560
|
+
.optional()
|
|
3561
|
+
.describe('Whether the service area is active (default: true)'),
|
|
2885
3562
|
}, async (params) => {
|
|
2886
3563
|
try {
|
|
2887
3564
|
const result = await client.create('service-areas', buildBody(params));
|
|
@@ -2971,24 +3648,65 @@ export function registerWriteTools(server, client) {
|
|
|
2971
3648
|
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
3649
|
service_area_id: z.string().uuid().describe('Service area ID (required)'),
|
|
2973
3650
|
name: z.string().min(1).max(500).describe('Measure name (required, unique per service area)'),
|
|
2974
|
-
category: z
|
|
3651
|
+
category: z
|
|
3652
|
+
.enum([
|
|
3653
|
+
'quality',
|
|
3654
|
+
'reliability',
|
|
3655
|
+
'responsiveness',
|
|
3656
|
+
'safety',
|
|
3657
|
+
'sustainability',
|
|
3658
|
+
'cost_efficiency',
|
|
3659
|
+
'capacity',
|
|
3660
|
+
])
|
|
3661
|
+
.describe('Measure category (required)'),
|
|
2975
3662
|
type: z.enum(['community', 'technical']).describe('Measure type (required)'),
|
|
2976
|
-
data_source: z
|
|
2977
|
-
|
|
2978
|
-
'
|
|
2979
|
-
'
|
|
2980
|
-
'
|
|
3663
|
+
data_source: z
|
|
3664
|
+
.enum([
|
|
3665
|
+
'manual',
|
|
3666
|
+
'custom_formula',
|
|
3667
|
+
'asset_condition_avg',
|
|
3668
|
+
'asset_condition_pct_above',
|
|
3669
|
+
'asset_condition_pct_below',
|
|
3670
|
+
'risk_score_avg',
|
|
3671
|
+
'risk_pct_critical',
|
|
3672
|
+
'wo_response_time_avg',
|
|
3673
|
+
'wo_completion_time_avg',
|
|
3674
|
+
'wo_backlog_count',
|
|
3675
|
+
'wo_overdue_count',
|
|
3676
|
+
'pm_compliance_rate',
|
|
3677
|
+
'compliance_score',
|
|
3678
|
+
'fci',
|
|
3679
|
+
'deferred_maintenance_ratio',
|
|
2981
3680
|
'asset_past_useful_life_pct',
|
|
2982
|
-
])
|
|
3681
|
+
])
|
|
3682
|
+
.describe('Data source type (required). Use "manual" if values will be entered by hand.'),
|
|
2983
3683
|
description: z.string().max(2000).optional().describe('Description'),
|
|
2984
|
-
community_statement: z
|
|
2985
|
-
|
|
2986
|
-
|
|
3684
|
+
community_statement: z
|
|
3685
|
+
.string()
|
|
3686
|
+
.max(2000)
|
|
3687
|
+
.optional()
|
|
3688
|
+
.describe('Community-facing statement (for community type measures)'),
|
|
3689
|
+
unit: z
|
|
3690
|
+
.string()
|
|
3691
|
+
.max(100)
|
|
3692
|
+
.optional()
|
|
3693
|
+
.describe('Unit of measurement (e.g., "%", "hours", "count")'),
|
|
3694
|
+
trend_direction: z
|
|
3695
|
+
.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal'])
|
|
3696
|
+
.optional()
|
|
3697
|
+
.describe('Which direction is better'),
|
|
2987
3698
|
target_value: z.number().optional().describe('Target value'),
|
|
2988
3699
|
minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
|
|
2989
3700
|
stretch_goal: z.number().optional().describe('Stretch goal value'),
|
|
2990
|
-
weight: z
|
|
2991
|
-
|
|
3701
|
+
weight: z
|
|
3702
|
+
.number()
|
|
3703
|
+
.min(0)
|
|
3704
|
+
.optional()
|
|
3705
|
+
.describe('Weight for composite score calculation (default: 1.0)'),
|
|
3706
|
+
data_source_config: z
|
|
3707
|
+
.record(z.unknown())
|
|
3708
|
+
.optional()
|
|
3709
|
+
.describe('Data source configuration (JSONB). E.g., {"threshold": 3} for pct_above/below, {"days_back": 90} for WO metrics.'),
|
|
2992
3710
|
is_active: z.boolean().optional().describe('Whether the measure is active (default: true)'),
|
|
2993
3711
|
sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
|
|
2994
3712
|
}, async (params) => {
|
|
@@ -3003,24 +3721,55 @@ export function registerWriteTools(server, client) {
|
|
|
3003
3721
|
server.tool('update_los_measure', 'Update an existing LoS measure by ID. Requires los_measures:write scope.', {
|
|
3004
3722
|
id: z.string().uuid().describe('LoS measure ID'),
|
|
3005
3723
|
name: z.string().min(1).max(500).optional().describe('Measure name'),
|
|
3006
|
-
category: z
|
|
3724
|
+
category: z
|
|
3725
|
+
.enum([
|
|
3726
|
+
'quality',
|
|
3727
|
+
'reliability',
|
|
3728
|
+
'responsiveness',
|
|
3729
|
+
'safety',
|
|
3730
|
+
'sustainability',
|
|
3731
|
+
'cost_efficiency',
|
|
3732
|
+
'capacity',
|
|
3733
|
+
])
|
|
3734
|
+
.optional()
|
|
3735
|
+
.describe('Measure category'),
|
|
3007
3736
|
type: z.enum(['community', 'technical']).optional().describe('Measure type'),
|
|
3008
|
-
data_source: z
|
|
3009
|
-
|
|
3010
|
-
'
|
|
3011
|
-
'
|
|
3012
|
-
'
|
|
3737
|
+
data_source: z
|
|
3738
|
+
.enum([
|
|
3739
|
+
'manual',
|
|
3740
|
+
'custom_formula',
|
|
3741
|
+
'asset_condition_avg',
|
|
3742
|
+
'asset_condition_pct_above',
|
|
3743
|
+
'asset_condition_pct_below',
|
|
3744
|
+
'risk_score_avg',
|
|
3745
|
+
'risk_pct_critical',
|
|
3746
|
+
'wo_response_time_avg',
|
|
3747
|
+
'wo_completion_time_avg',
|
|
3748
|
+
'wo_backlog_count',
|
|
3749
|
+
'wo_overdue_count',
|
|
3750
|
+
'pm_compliance_rate',
|
|
3751
|
+
'compliance_score',
|
|
3752
|
+
'fci',
|
|
3753
|
+
'deferred_maintenance_ratio',
|
|
3013
3754
|
'asset_past_useful_life_pct',
|
|
3014
|
-
])
|
|
3755
|
+
])
|
|
3756
|
+
.optional()
|
|
3757
|
+
.describe('Data source type'),
|
|
3015
3758
|
description: z.string().max(2000).optional().describe('Description'),
|
|
3016
3759
|
community_statement: z.string().max(2000).optional().describe('Community-facing statement'),
|
|
3017
3760
|
unit: z.string().max(100).optional().describe('Unit of measurement'),
|
|
3018
|
-
trend_direction: z
|
|
3761
|
+
trend_direction: z
|
|
3762
|
+
.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal'])
|
|
3763
|
+
.optional()
|
|
3764
|
+
.describe('Which direction is better'),
|
|
3019
3765
|
target_value: z.number().optional().describe('Target value'),
|
|
3020
3766
|
minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
|
|
3021
3767
|
stretch_goal: z.number().optional().describe('Stretch goal value'),
|
|
3022
3768
|
weight: z.number().min(0).optional().describe('Weight for composite score calculation'),
|
|
3023
|
-
data_source_config: z
|
|
3769
|
+
data_source_config: z
|
|
3770
|
+
.record(z.unknown())
|
|
3771
|
+
.optional()
|
|
3772
|
+
.describe('Data source configuration (JSONB)'),
|
|
3024
3773
|
is_active: z.boolean().optional().describe('Active status'),
|
|
3025
3774
|
sort_order: z.number().int().min(0).optional().describe('Sort order'),
|
|
3026
3775
|
}, async ({ id, ...rest }) => {
|
|
@@ -3046,12 +3795,19 @@ export function registerWriteTools(server, client) {
|
|
|
3046
3795
|
// ============================================================
|
|
3047
3796
|
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
3797
|
los_measure_id: z.string().uuid().describe('LoS measure ID (required)'),
|
|
3049
|
-
period_type: z
|
|
3050
|
-
|
|
3798
|
+
period_type: z
|
|
3799
|
+
.enum(['monthly', 'quarterly', 'semi_annual', 'annual'])
|
|
3800
|
+
.describe('Period type (required)'),
|
|
3801
|
+
period_start: z
|
|
3802
|
+
.string()
|
|
3803
|
+
.describe('Period start date (ISO 8601, required, e.g., "2026-01-01")'),
|
|
3051
3804
|
period_end: z.string().describe('Period end date (ISO 8601, required, e.g., "2026-03-31")'),
|
|
3052
3805
|
actual_value: z.number().describe('Measured value (required)'),
|
|
3053
3806
|
notes: z.string().max(2000).optional().describe('Notes or context for this measurement'),
|
|
3054
|
-
is_auto: z
|
|
3807
|
+
is_auto: z
|
|
3808
|
+
.boolean()
|
|
3809
|
+
.optional()
|
|
3810
|
+
.describe('Whether this is an auto-calculated value (default: false)'),
|
|
3055
3811
|
}, async (params) => {
|
|
3056
3812
|
try {
|
|
3057
3813
|
const result = await client.create('los-measurements', buildBody(params));
|
|
@@ -3065,7 +3821,10 @@ export function registerWriteTools(server, client) {
|
|
|
3065
3821
|
id: z.string().uuid().describe('LoS measurement ID'),
|
|
3066
3822
|
actual_value: z.number().optional().describe('Measured value'),
|
|
3067
3823
|
notes: z.string().max(2000).optional().describe('Notes or context'),
|
|
3068
|
-
period_type: z
|
|
3824
|
+
period_type: z
|
|
3825
|
+
.enum(['monthly', 'quarterly', 'semi_annual', 'annual'])
|
|
3826
|
+
.optional()
|
|
3827
|
+
.describe('Period type'),
|
|
3069
3828
|
period_start: z.string().optional().describe('Period start date (ISO 8601)'),
|
|
3070
3829
|
period_end: z.string().optional().describe('Period end date (ISO 8601)'),
|
|
3071
3830
|
is_auto: z.boolean().optional().describe('Whether this is auto-calculated'),
|
|
@@ -3097,14 +3856,39 @@ export function registerWriteTools(server, client) {
|
|
|
3097
3856
|
.min(3)
|
|
3098
3857
|
.describe('Polygon outline as an array of [x, y] points in normalized 0-1 coordinates (origin top-left). At least 3 points.');
|
|
3099
3858
|
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
|
-
|
|
3859
|
+
building_id: z
|
|
3860
|
+
.string()
|
|
3861
|
+
.uuid()
|
|
3862
|
+
.optional()
|
|
3863
|
+
.describe('Building this floor belongs to (omit if site-scoped)'),
|
|
3864
|
+
site_id: z
|
|
3865
|
+
.string()
|
|
3866
|
+
.uuid()
|
|
3867
|
+
.optional()
|
|
3868
|
+
.describe('Site this plan belongs to (use for site-level / campus plans; omit if building-scoped)'),
|
|
3869
|
+
floor_label: z
|
|
3870
|
+
.string()
|
|
3871
|
+
.max(200)
|
|
3872
|
+
.describe('Human-readable label (e.g. "Ground Floor", "Mezzanine", "Site Plan")'),
|
|
3873
|
+
floor_order: z
|
|
3874
|
+
.number()
|
|
3875
|
+
.int()
|
|
3876
|
+
.min(0)
|
|
3877
|
+
.optional()
|
|
3878
|
+
.describe('Sort order within the scope (lowest first)'),
|
|
3104
3879
|
pdf_storage_path: z.string().max(1000).describe('Supabase Storage path to the PDF file'),
|
|
3105
3880
|
pdf_filename: z.string().max(500).describe('Original filename for display'),
|
|
3106
|
-
page_number: z
|
|
3107
|
-
|
|
3881
|
+
page_number: z
|
|
3882
|
+
.number()
|
|
3883
|
+
.int()
|
|
3884
|
+
.min(1)
|
|
3885
|
+
.optional()
|
|
3886
|
+
.describe('1-indexed page number within the PDF'),
|
|
3887
|
+
page_width_pt: z
|
|
3888
|
+
.number()
|
|
3889
|
+
.min(0)
|
|
3890
|
+
.optional()
|
|
3891
|
+
.describe('Page width in PDF points (discovered client-side)'),
|
|
3108
3892
|
page_height_pt: z.number().min(0).optional().describe('Page height in PDF points'),
|
|
3109
3893
|
status: z.enum(FLOORPLAN_STATUSES).optional().describe('Detection status (default: pending)'),
|
|
3110
3894
|
}, async (params) => {
|
|
@@ -3149,10 +3933,25 @@ export function registerWriteTools(server, client) {
|
|
|
3149
3933
|
floorplan_id: z.string().uuid().describe('Floorplan this region belongs to'),
|
|
3150
3934
|
label: z.string().max(500).describe('Region label (e.g. "Boiler Room 2B")'),
|
|
3151
3935
|
polygon: polygonSchema,
|
|
3152
|
-
location_id: z
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3936
|
+
location_id: z
|
|
3937
|
+
.string()
|
|
3938
|
+
.uuid()
|
|
3939
|
+
.optional()
|
|
3940
|
+
.describe('Linked Location ID (resolved via list_locations)'),
|
|
3941
|
+
source: z
|
|
3942
|
+
.enum(REGION_SOURCES)
|
|
3943
|
+
.optional()
|
|
3944
|
+
.describe('"manual" (default) or "ai" for AI-detected'),
|
|
3945
|
+
confidence: z
|
|
3946
|
+
.number()
|
|
3947
|
+
.min(0)
|
|
3948
|
+
.max(1)
|
|
3949
|
+
.optional()
|
|
3950
|
+
.describe('AI confidence score (0-1), only set when source=ai'),
|
|
3951
|
+
reviewed: z
|
|
3952
|
+
.boolean()
|
|
3953
|
+
.optional()
|
|
3954
|
+
.describe('True if an admin has reviewed this region (default: true for manual, false for ai)'),
|
|
3156
3955
|
}, async (params) => {
|
|
3157
3956
|
try {
|
|
3158
3957
|
const result = await client.create('floorplan-regions', buildBody(params));
|
|
@@ -3166,7 +3965,11 @@ export function registerWriteTools(server, client) {
|
|
|
3166
3965
|
id: z.string().uuid().describe('Floorplan region ID'),
|
|
3167
3966
|
label: z.string().max(500).optional().describe('New label'),
|
|
3168
3967
|
polygon: polygonSchema.optional(),
|
|
3169
|
-
location_id: z
|
|
3968
|
+
location_id: z
|
|
3969
|
+
.string()
|
|
3970
|
+
.uuid()
|
|
3971
|
+
.optional()
|
|
3972
|
+
.describe('Linked Location ID (set to null to unlink)'),
|
|
3170
3973
|
confidence: z.number().min(0).max(1).optional(),
|
|
3171
3974
|
reviewed: z.boolean().optional().describe('Mark region as reviewed by admin'),
|
|
3172
3975
|
}, async ({ id, ...rest }) => {
|
|
@@ -3192,8 +3995,15 @@ export function registerWriteTools(server, client) {
|
|
|
3192
3995
|
floorplan_id: z.string().uuid().describe('Target floorplan (resolve via list_floorplans)'),
|
|
3193
3996
|
x: z.number().min(0).max(1).describe('Normalized x coordinate (0=left, 1=right)'),
|
|
3194
3997
|
y: z.number().min(0).max(1).describe('Normalized y coordinate (0=top, 1=bottom)'),
|
|
3195
|
-
region_id: z
|
|
3196
|
-
|
|
3998
|
+
region_id: z
|
|
3999
|
+
.string()
|
|
4000
|
+
.uuid()
|
|
4001
|
+
.optional()
|
|
4002
|
+
.describe('Optional region the pin sits inside (usually auto-inferred)'),
|
|
4003
|
+
source: z
|
|
4004
|
+
.enum(REGION_SOURCES)
|
|
4005
|
+
.optional()
|
|
4006
|
+
.describe('"manual" (default) or "ai" for AI-placed'),
|
|
3197
4007
|
}, async (params) => {
|
|
3198
4008
|
try {
|
|
3199
4009
|
const result = await client.create('asset-placements', buildBody(params));
|
|
@@ -3231,30 +4041,80 @@ export function registerWriteTools(server, client) {
|
|
|
3231
4041
|
// Bulk operations
|
|
3232
4042
|
// ============================================================
|
|
3233
4043
|
const BULK_RESOURCES = [
|
|
3234
|
-
'assets',
|
|
3235
|
-
'
|
|
3236
|
-
'
|
|
3237
|
-
'
|
|
3238
|
-
'
|
|
3239
|
-
'
|
|
3240
|
-
'
|
|
3241
|
-
'
|
|
3242
|
-
'
|
|
3243
|
-
'
|
|
3244
|
-
'
|
|
3245
|
-
'
|
|
3246
|
-
'
|
|
3247
|
-
'
|
|
3248
|
-
'
|
|
3249
|
-
'
|
|
3250
|
-
'
|
|
3251
|
-
'
|
|
3252
|
-
'
|
|
3253
|
-
'
|
|
4044
|
+
'assets',
|
|
4045
|
+
'work-orders',
|
|
4046
|
+
'work-requests',
|
|
4047
|
+
'vendors',
|
|
4048
|
+
'sites',
|
|
4049
|
+
'buildings',
|
|
4050
|
+
'locations',
|
|
4051
|
+
'systems',
|
|
4052
|
+
'system-groups',
|
|
4053
|
+
'system-classes',
|
|
4054
|
+
'pm-schedules',
|
|
4055
|
+
'pm-templates',
|
|
4056
|
+
'projects',
|
|
4057
|
+
'contracts',
|
|
4058
|
+
'invoices',
|
|
4059
|
+
'purchase-orders',
|
|
4060
|
+
'expenses',
|
|
4061
|
+
'budgets',
|
|
4062
|
+
'asset-types',
|
|
4063
|
+
'asset-type-groups',
|
|
4064
|
+
'asset-statuses',
|
|
4065
|
+
'work-categories',
|
|
4066
|
+
'manufacturers',
|
|
4067
|
+
'building-types',
|
|
4068
|
+
'location-types',
|
|
4069
|
+
'cost-categories',
|
|
4070
|
+
'compliance',
|
|
4071
|
+
'compliance-records',
|
|
4072
|
+
'asset-comments',
|
|
4073
|
+
'asset-costs',
|
|
4074
|
+
'asset-replacement-plans',
|
|
4075
|
+
'work-order-comments',
|
|
4076
|
+
'project-tasks',
|
|
4077
|
+
'project-milestones',
|
|
4078
|
+
'project-phases',
|
|
4079
|
+
'project-budget-items',
|
|
4080
|
+
'project-time-entries',
|
|
4081
|
+
'project-comments',
|
|
4082
|
+
'project-team-members',
|
|
4083
|
+
'project-task-dependencies',
|
|
4084
|
+
'project-updates',
|
|
4085
|
+
'project-cost-snapshots',
|
|
4086
|
+
'project-locations',
|
|
4087
|
+
'project-sites',
|
|
4088
|
+
'project-buildings',
|
|
4089
|
+
'project-systems',
|
|
4090
|
+
'project-system-classes',
|
|
4091
|
+
'project-system-groups',
|
|
4092
|
+
'project-assets',
|
|
4093
|
+
'project-risks',
|
|
4094
|
+
'parts',
|
|
4095
|
+
'part-categories',
|
|
4096
|
+
'custom-field-definitions',
|
|
4097
|
+
'custom-field-values',
|
|
4098
|
+
'vendor-site-assignments',
|
|
4099
|
+
'contract-sites',
|
|
4100
|
+
'asset-documents',
|
|
4101
|
+
'attachments',
|
|
4102
|
+
'project-documents',
|
|
4103
|
+
'contract-documents',
|
|
4104
|
+
'service-areas',
|
|
4105
|
+
'los-measures',
|
|
4106
|
+
'los-measurements',
|
|
4107
|
+
'floorplans',
|
|
4108
|
+
'floorplan-regions',
|
|
4109
|
+
'asset-placements',
|
|
3254
4110
|
];
|
|
3255
4111
|
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
4112
|
resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
|
|
3257
|
-
items: z
|
|
4113
|
+
items: z
|
|
4114
|
+
.array(z.record(z.unknown()))
|
|
4115
|
+
.min(1)
|
|
4116
|
+
.max(100)
|
|
4117
|
+
.describe('Array of objects to create (max 100). Each object uses the same fields as the single-create endpoint for that resource.'),
|
|
3258
4118
|
}, async ({ resource, items }) => {
|
|
3259
4119
|
try {
|
|
3260
4120
|
const result = await client.bulkCreate(resource, items);
|
|
@@ -3308,7 +4168,12 @@ export function registerWriteTools(server, client) {
|
|
|
3308
4168
|
name: z.string().max(500).describe('Compliance item name (required)'),
|
|
3309
4169
|
description: z.string().optional().describe('Description'),
|
|
3310
4170
|
regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
|
|
3311
|
-
compliance_period_months: z
|
|
4171
|
+
compliance_period_months: z
|
|
4172
|
+
.number()
|
|
4173
|
+
.int()
|
|
4174
|
+
.min(1)
|
|
4175
|
+
.optional()
|
|
4176
|
+
.describe('Compliance period in months'),
|
|
3312
4177
|
status: z.enum(['active', 'archived']).optional().describe('Status'),
|
|
3313
4178
|
system_id: z.string().uuid().optional().describe('Associated system ID'),
|
|
3314
4179
|
}, async (params) => {
|
|
@@ -3325,7 +4190,12 @@ export function registerWriteTools(server, client) {
|
|
|
3325
4190
|
name: z.string().max(500).optional().describe('Compliance item name'),
|
|
3326
4191
|
description: z.string().optional().describe('Description'),
|
|
3327
4192
|
regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
|
|
3328
|
-
compliance_period_months: z
|
|
4193
|
+
compliance_period_months: z
|
|
4194
|
+
.number()
|
|
4195
|
+
.int()
|
|
4196
|
+
.min(1)
|
|
4197
|
+
.optional()
|
|
4198
|
+
.describe('Compliance period in months'),
|
|
3329
4199
|
status: z.enum(['active', 'archived']).optional().describe('Status'),
|
|
3330
4200
|
system_id: z.string().uuid().optional().describe('Associated system ID'),
|
|
3331
4201
|
}, async ({ id, ...rest }) => {
|
|
@@ -3355,8 +4225,17 @@ export function registerWriteTools(server, client) {
|
|
|
3355
4225
|
work_order_id: z.string().uuid().describe('Work order ID (required)'),
|
|
3356
4226
|
completed_at: z.string().describe('Completion date-time (ISO 8601, required)'),
|
|
3357
4227
|
completed_by: z.string().max(200).optional().describe('User ID who completed'),
|
|
3358
|
-
required_frequency_days: z
|
|
3359
|
-
|
|
4228
|
+
required_frequency_days: z
|
|
4229
|
+
.number()
|
|
4230
|
+
.int()
|
|
4231
|
+
.min(1)
|
|
4232
|
+
.describe('Required frequency in days (required)'),
|
|
4233
|
+
days_since_last_completion: z
|
|
4234
|
+
.number()
|
|
4235
|
+
.int()
|
|
4236
|
+
.min(0)
|
|
4237
|
+
.optional()
|
|
4238
|
+
.describe('Days since last completion'),
|
|
3360
4239
|
}, async (params) => {
|
|
3361
4240
|
try {
|
|
3362
4241
|
const result = await client.create('compliance-records', buildBody(params));
|
|
@@ -3370,8 +4249,18 @@ export function registerWriteTools(server, client) {
|
|
|
3370
4249
|
id: z.string().uuid().describe('Compliance record ID'),
|
|
3371
4250
|
completed_at: z.string().optional().describe('Completion date-time (ISO 8601)'),
|
|
3372
4251
|
completed_by: z.string().max(200).optional().describe('User ID who completed'),
|
|
3373
|
-
required_frequency_days: z
|
|
3374
|
-
|
|
4252
|
+
required_frequency_days: z
|
|
4253
|
+
.number()
|
|
4254
|
+
.int()
|
|
4255
|
+
.min(1)
|
|
4256
|
+
.optional()
|
|
4257
|
+
.describe('Required frequency in days'),
|
|
4258
|
+
days_since_last_completion: z
|
|
4259
|
+
.number()
|
|
4260
|
+
.int()
|
|
4261
|
+
.min(0)
|
|
4262
|
+
.optional()
|
|
4263
|
+
.describe('Days since last completion'),
|
|
3375
4264
|
}, async ({ id, ...rest }) => {
|
|
3376
4265
|
try {
|
|
3377
4266
|
const result = await client.update('compliance-records', id, buildBody(rest));
|
|
@@ -3395,7 +4284,11 @@ export function registerWriteTools(server, client) {
|
|
|
3395
4284
|
// ============================================================
|
|
3396
4285
|
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
4286
|
resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
|
|
3398
|
-
items: z
|
|
4287
|
+
items: z
|
|
4288
|
+
.array(z.record(z.unknown()))
|
|
4289
|
+
.min(1)
|
|
4290
|
+
.max(100)
|
|
4291
|
+
.describe('Array of objects to update (max 100). Each must include an "id" field (UUID) plus fields to change.'),
|
|
3399
4292
|
}, async ({ resource, items }) => {
|
|
3400
4293
|
try {
|
|
3401
4294
|
const result = await client.bulkUpdate(resource, items);
|