@assetlab/mcp-server 1.20.0 → 1.23.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/LICENSE +21 -21
- package/README.md +8 -0
- package/dist/response-shaping.js +12 -3
- package/dist/response-shaping.js.map +1 -1
- package/dist/tools-write.js +590 -2
- package/dist/tools-write.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +383 -4
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/tools-write.js
CHANGED
|
@@ -1669,7 +1669,7 @@ export function registerWriteTools(server, client) {
|
|
|
1669
1669
|
// ============================================================
|
|
1670
1670
|
// 18. Asset Costs (scope: asset_costs)
|
|
1671
1671
|
// ============================================================
|
|
1672
|
-
server.tool('create_asset_cost', 'Create a new asset cost entry. Requires asset_costs:write scope.', {
|
|
1672
|
+
server.tool('create_asset_cost', 'Create a new asset cost entry — the record type shown on the AssetLab "Expenses" page. Requires asset_costs:write scope.', {
|
|
1673
1673
|
category: z
|
|
1674
1674
|
.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other'])
|
|
1675
1675
|
.describe('Cost category (required)'),
|
|
@@ -1680,6 +1680,8 @@ export function registerWriteTools(server, client) {
|
|
|
1680
1680
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
1681
1681
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
1682
1682
|
description: z.string().optional().describe('Description'),
|
|
1683
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number (free text)'),
|
|
1684
|
+
po_number: z.string().max(200).optional().describe('Purchase order number (free text)'),
|
|
1683
1685
|
}, async (params) => {
|
|
1684
1686
|
try {
|
|
1685
1687
|
const result = await client.create('asset-costs', buildBody(params));
|
|
@@ -1689,7 +1691,7 @@ export function registerWriteTools(server, client) {
|
|
|
1689
1691
|
return formatError(err);
|
|
1690
1692
|
}
|
|
1691
1693
|
});
|
|
1692
|
-
server.tool('update_asset_cost', 'Update an existing asset cost entry by ID. Requires asset_costs:write scope.', {
|
|
1694
|
+
server.tool('update_asset_cost', 'Update an existing asset cost entry by ID — the record type shown on the AssetLab "Expenses" page. Requires asset_costs:write scope.', {
|
|
1693
1695
|
id: z.string().uuid().describe('Asset cost ID'),
|
|
1694
1696
|
category: z
|
|
1695
1697
|
.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other'])
|
|
@@ -1702,6 +1704,8 @@ export function registerWriteTools(server, client) {
|
|
|
1702
1704
|
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
1703
1705
|
work_order_id: z.string().uuid().optional().describe('Work order ID'),
|
|
1704
1706
|
description: z.string().optional().describe('Description'),
|
|
1707
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number (free text)'),
|
|
1708
|
+
po_number: z.string().max(200).optional().describe('Purchase order number (free text)'),
|
|
1705
1709
|
}, async ({ id, ...rest }) => {
|
|
1706
1710
|
try {
|
|
1707
1711
|
const result = await client.update('asset-costs', id, buildBody(rest));
|
|
@@ -4107,6 +4111,7 @@ export function registerWriteTools(server, client) {
|
|
|
4107
4111
|
'floorplans',
|
|
4108
4112
|
'floorplan-regions',
|
|
4109
4113
|
'asset-placements',
|
|
4114
|
+
'infrastructure-assets',
|
|
4110
4115
|
];
|
|
4111
4116
|
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.', {
|
|
4112
4117
|
resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
|
|
@@ -4280,6 +4285,589 @@ export function registerWriteTools(server, client) {
|
|
|
4280
4285
|
}
|
|
4281
4286
|
});
|
|
4282
4287
|
// ============================================================
|
|
4288
|
+
// Infrastructure Asset Classes (scope: infrastructure_asset_classes)
|
|
4289
|
+
// ============================================================
|
|
4290
|
+
const INFRA_ASSET_CLASS_CATEGORIES = [
|
|
4291
|
+
'pavement',
|
|
4292
|
+
'sewer',
|
|
4293
|
+
'water',
|
|
4294
|
+
'gas',
|
|
4295
|
+
'electrical',
|
|
4296
|
+
'telecom',
|
|
4297
|
+
'structure',
|
|
4298
|
+
'furniture',
|
|
4299
|
+
'other',
|
|
4300
|
+
];
|
|
4301
|
+
const assetClassCode = z
|
|
4302
|
+
.string()
|
|
4303
|
+
.regex(/^[a-z][a-z0-9_]{0,49}$/)
|
|
4304
|
+
.describe('Asset class code (lowercase snake_case, 1-50 chars)');
|
|
4305
|
+
server.tool('create_infrastructure_asset_class', 'Create a tenant-defined infrastructure asset class. Builtin classes are managed by AssetLab and cannot be created via API (is_builtin is always forced to false). Requires infrastructure_asset_classes:write scope.', {
|
|
4306
|
+
code: assetClassCode,
|
|
4307
|
+
name: z.string().max(200).describe('Display name (required)'),
|
|
4308
|
+
category: z
|
|
4309
|
+
.enum(INFRA_ASSET_CLASS_CATEGORIES)
|
|
4310
|
+
.describe('Category (pavement, sewer, water, gas, electrical, telecom, structure, furniture, other)'),
|
|
4311
|
+
description: z.string().optional().describe('Description'),
|
|
4312
|
+
icon: z.string().max(50).optional().describe('Icon name'),
|
|
4313
|
+
color: z.string().max(20).optional().describe('Display color (CSS color or hex)'),
|
|
4314
|
+
sort_order: z.number().int().optional().describe('Sort order'),
|
|
4315
|
+
}, async (params) => {
|
|
4316
|
+
try {
|
|
4317
|
+
const result = await client.create('infrastructure-asset-classes', buildBody(params));
|
|
4318
|
+
return formatResult(result);
|
|
4319
|
+
}
|
|
4320
|
+
catch (err) {
|
|
4321
|
+
return formatError(err);
|
|
4322
|
+
}
|
|
4323
|
+
});
|
|
4324
|
+
server.tool('update_infrastructure_asset_class', 'Update a tenant-defined infrastructure asset class (addressed by code). The `code`, `category`, and `is_builtin` fields are immutable; attempts to change them on a builtin class return 409. Requires infrastructure_asset_classes:write scope.', {
|
|
4325
|
+
code: assetClassCode,
|
|
4326
|
+
name: z.string().max(200).optional().describe('Display name'),
|
|
4327
|
+
description: z.string().optional().describe('Description'),
|
|
4328
|
+
icon: z.string().max(50).optional().describe('Icon name'),
|
|
4329
|
+
color: z.string().max(20).optional().describe('Display color'),
|
|
4330
|
+
sort_order: z.number().int().optional().describe('Sort order'),
|
|
4331
|
+
}, async ({ code, ...rest }) => {
|
|
4332
|
+
try {
|
|
4333
|
+
const result = await client.update('infrastructure-asset-classes', code, buildBody(rest));
|
|
4334
|
+
return formatResult(result);
|
|
4335
|
+
}
|
|
4336
|
+
catch (err) {
|
|
4337
|
+
return formatError(err);
|
|
4338
|
+
}
|
|
4339
|
+
});
|
|
4340
|
+
server.tool('delete_infrastructure_asset_class', 'Delete a tenant-defined infrastructure asset class by code. Builtin classes cannot be deleted. Classes referenced by any infrastructure network are protected by FK and cannot be deleted until those networks are reassigned. Requires infrastructure_asset_classes:write scope.', { code: assetClassCode }, async ({ code }) => {
|
|
4341
|
+
try {
|
|
4342
|
+
const result = await client.remove('infrastructure-asset-classes', code);
|
|
4343
|
+
return formatResult(result);
|
|
4344
|
+
}
|
|
4345
|
+
catch (err) {
|
|
4346
|
+
return formatError(err);
|
|
4347
|
+
}
|
|
4348
|
+
});
|
|
4349
|
+
// ============================================================
|
|
4350
|
+
// Infrastructure Networks (scope: infrastructure_networks)
|
|
4351
|
+
// ============================================================
|
|
4352
|
+
server.tool('create_infrastructure_network', 'Create an infrastructure network — a named grouping of features bound to one asset class. Requires infrastructure_networks:write scope.', {
|
|
4353
|
+
name: z.string().max(200).describe('Network name (required)'),
|
|
4354
|
+
asset_class: assetClassCode.describe('Asset class code (must exist; resolve via list_infrastructure_asset_classes)'),
|
|
4355
|
+
description: z.string().optional().describe('Description'),
|
|
4356
|
+
color_scheme: z.string().max(50).optional().describe('Display color scheme'),
|
|
4357
|
+
metadata: z.record(z.unknown()).optional().describe('Free-form JSON metadata'),
|
|
4358
|
+
}, async (params) => {
|
|
4359
|
+
try {
|
|
4360
|
+
const result = await client.create('infrastructure-networks', buildBody(params));
|
|
4361
|
+
return formatResult(result);
|
|
4362
|
+
}
|
|
4363
|
+
catch (err) {
|
|
4364
|
+
return formatError(err);
|
|
4365
|
+
}
|
|
4366
|
+
});
|
|
4367
|
+
server.tool('update_infrastructure_network', 'Update an existing infrastructure network by ID. Requires infrastructure_networks:write scope.', {
|
|
4368
|
+
id: z.string().uuid().describe('Infrastructure network ID'),
|
|
4369
|
+
name: z.string().max(200).optional().describe('Network name'),
|
|
4370
|
+
asset_class: assetClassCode.optional().describe('Asset class code'),
|
|
4371
|
+
description: z.string().optional().describe('Description'),
|
|
4372
|
+
color_scheme: z.string().max(50).optional().describe('Display color scheme'),
|
|
4373
|
+
metadata: z.record(z.unknown()).optional().describe('Free-form JSON metadata'),
|
|
4374
|
+
}, async ({ id, ...rest }) => {
|
|
4375
|
+
try {
|
|
4376
|
+
const result = await client.update('infrastructure-networks', id, buildBody(rest));
|
|
4377
|
+
return formatResult(result);
|
|
4378
|
+
}
|
|
4379
|
+
catch (err) {
|
|
4380
|
+
return formatError(err);
|
|
4381
|
+
}
|
|
4382
|
+
});
|
|
4383
|
+
server.tool('delete_infrastructure_network', 'Delete an infrastructure network by ID. Cascades to all features in the network — confirm with the user before deleting. Requires infrastructure_networks:write scope.', { id: z.string().uuid().describe('Infrastructure network ID') }, async ({ id }) => {
|
|
4384
|
+
try {
|
|
4385
|
+
const result = await client.remove('infrastructure-networks', id);
|
|
4386
|
+
return formatResult(result);
|
|
4387
|
+
}
|
|
4388
|
+
catch (err) {
|
|
4389
|
+
return formatError(err);
|
|
4390
|
+
}
|
|
4391
|
+
});
|
|
4392
|
+
// ============================================================
|
|
4393
|
+
// Infrastructure Assets / Features (scope: infrastructure_assets)
|
|
4394
|
+
// ============================================================
|
|
4395
|
+
const pointGeometry = z.object({
|
|
4396
|
+
type: z.literal('Point'),
|
|
4397
|
+
coordinates: z.tuple([z.number(), z.number()]),
|
|
4398
|
+
});
|
|
4399
|
+
const lineStringGeometry = z.object({
|
|
4400
|
+
type: z.literal('LineString'),
|
|
4401
|
+
coordinates: z.array(z.tuple([z.number(), z.number()])).min(2),
|
|
4402
|
+
});
|
|
4403
|
+
const featureGeometry = z.union([pointGeometry, lineStringGeometry]);
|
|
4404
|
+
const FEATURE_TYPES = ['segment', 'node'];
|
|
4405
|
+
const FLOW_DIRECTIONS = ['with_geometry', 'against_geometry'];
|
|
4406
|
+
const POSITIONAL_ACCURACY = ['survey_grade', 'mapping_grade', 'sketch', 'unknown'];
|
|
4407
|
+
server.tool('create_infrastructure_asset', 'Create an infrastructure asset (feature — segment or node). Geometry must be GeoJSON Point (for nodes) or LineString (for segments) in EPSG:4326; coordinates are [longitude, latitude]. `length_m`, `slope_pct`, and `risk_score` are computed server-side. Requires infrastructure_assets:write scope.', {
|
|
4408
|
+
network_id: z.string().uuid().describe('Infrastructure network ID (required)'),
|
|
4409
|
+
feature_type: z.enum(FEATURE_TYPES).describe('"segment" (LineString) or "node" (Point)'),
|
|
4410
|
+
geometry: featureGeometry.describe('GeoJSON geometry — Point for nodes, LineString for segments'),
|
|
4411
|
+
name: z.string().max(500).optional().describe('Feature name'),
|
|
4412
|
+
description: z.string().optional().describe('Description'),
|
|
4413
|
+
external_ids: z.record(z.unknown()).optional().describe('Free-form external ID map'),
|
|
4414
|
+
from_street: z.string().max(200).optional().describe('From street (segments)'),
|
|
4415
|
+
to_street: z.string().max(200).optional().describe('To street (segments)'),
|
|
4416
|
+
image_url: z.string().optional().describe('Image URL'),
|
|
4417
|
+
qr_code: z.string().max(200).optional().describe('QR code'),
|
|
4418
|
+
from_feature_id: z.string().uuid().optional().describe('From-node feature ID (segments)'),
|
|
4419
|
+
to_feature_id: z.string().uuid().optional().describe('To-node feature ID (segments)'),
|
|
4420
|
+
flow_direction: z.enum(FLOW_DIRECTIONS).optional().describe('Flow direction'),
|
|
4421
|
+
asset_type_id: z.string().uuid().optional().describe('Asset type ID'),
|
|
4422
|
+
manufacturer_id: z.string().uuid().optional().describe('Manufacturer ID'),
|
|
4423
|
+
system_class_id: z.string().uuid().optional().describe('System class ID'),
|
|
4424
|
+
system_group_id: z.string().uuid().optional().describe('System group ID'),
|
|
4425
|
+
system_id: z.string().uuid().optional().describe('System ID'),
|
|
4426
|
+
model: z.string().max(200).optional().describe('Model'),
|
|
4427
|
+
serial_number: z.string().max(200).optional().describe('Serial number'),
|
|
4428
|
+
site_id: z.string().uuid().optional().describe('Site ID'),
|
|
4429
|
+
building_id: z.string().uuid().optional().describe('Building ID'),
|
|
4430
|
+
location_id: z.string().uuid().optional().describe('Location ID'),
|
|
4431
|
+
status_id: z.string().optional().describe('Asset status ID'),
|
|
4432
|
+
condition_score: z.number().min(0).max(100).optional().describe('Condition score (0-100)'),
|
|
4433
|
+
last_maintenance_date: z.string().optional().describe('Last maintenance date (YYYY-MM-DD)'),
|
|
4434
|
+
risk_factor: z.string().optional().describe('Risk factor: CRITICAL, HIGH, MEDIUM, LOW'),
|
|
4435
|
+
consequence_of_failure_score: z.number().min(0).max(100).optional(),
|
|
4436
|
+
likelihood_of_failure_score: z.number().min(0).max(100).optional(),
|
|
4437
|
+
safety_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4438
|
+
service_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4439
|
+
environmental_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4440
|
+
financial_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4441
|
+
regulatory_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4442
|
+
reputation_impact: z.string().optional().describe('LOW, MEDIUM, HIGH, CRITICAL'),
|
|
4443
|
+
purchase_cost: z.number().min(0).optional().describe('Purchase cost'),
|
|
4444
|
+
purchase_date: z.string().optional().describe('Purchase date (YYYY-MM-DD)'),
|
|
4445
|
+
install_date: z.string().optional().describe('Install date (YYYY-MM-DD)'),
|
|
4446
|
+
expected_lifetime_years: z.number().min(0).optional().describe('Expected lifetime (years)'),
|
|
4447
|
+
salvage_value: z.number().min(0).optional().describe('Salvage value'),
|
|
4448
|
+
salvage_value_percentage: z.number().min(0).max(100).optional(),
|
|
4449
|
+
quantity: z.number().min(0).optional().describe('Quantity'),
|
|
4450
|
+
unit_of_measure: z.string().max(50).optional().describe('Unit of measure'),
|
|
4451
|
+
unit_replacement_value: z.number().min(0).optional().describe('Unit replacement value'),
|
|
4452
|
+
purchase_cost_calculation_method: z
|
|
4453
|
+
.enum(['manual', 'calculated_sqft', 'calculated_unit'])
|
|
4454
|
+
.optional()
|
|
4455
|
+
.describe('Cost calc method'),
|
|
4456
|
+
material: z.string().max(200).optional().describe('Material'),
|
|
4457
|
+
diameter_mm: z.number().min(0).optional().describe('Diameter (mm)'),
|
|
4458
|
+
width_m: z.number().min(0).optional().describe('Width (m)'),
|
|
4459
|
+
lanes: z.number().int().min(0).optional().describe('Lane count'),
|
|
4460
|
+
depth_m: z.number().min(0).optional().describe('Depth (m)'),
|
|
4461
|
+
from_invert_m: z.number().optional().describe('From-invert elevation (m)'),
|
|
4462
|
+
to_invert_m: z.number().optional().describe('To-invert elevation (m)'),
|
|
4463
|
+
positional_accuracy_class: z
|
|
4464
|
+
.enum(POSITIONAL_ACCURACY)
|
|
4465
|
+
.optional()
|
|
4466
|
+
.describe('Positional accuracy class'),
|
|
4467
|
+
data_source: z.string().max(200).optional().describe('Data source'),
|
|
4468
|
+
}, async (params) => {
|
|
4469
|
+
try {
|
|
4470
|
+
const result = await client.create('infrastructure-assets', buildBody(params));
|
|
4471
|
+
return formatResult(result);
|
|
4472
|
+
}
|
|
4473
|
+
catch (err) {
|
|
4474
|
+
return formatError(err);
|
|
4475
|
+
}
|
|
4476
|
+
});
|
|
4477
|
+
server.tool('update_infrastructure_asset', 'Update an existing infrastructure asset (feature) by ID. To change geometry, provide a new GeoJSON Point/LineString matching the existing feature_type. Computed columns (length_m, slope_pct, risk_score) cannot be set. Requires infrastructure_assets:write scope.', {
|
|
4478
|
+
id: z.string().uuid().describe('Infrastructure asset ID'),
|
|
4479
|
+
geometry: featureGeometry.optional().describe('Replacement GeoJSON geometry'),
|
|
4480
|
+
name: z.string().max(500).optional(),
|
|
4481
|
+
description: z.string().optional(),
|
|
4482
|
+
external_ids: z.record(z.unknown()).optional(),
|
|
4483
|
+
from_street: z.string().max(200).optional(),
|
|
4484
|
+
to_street: z.string().max(200).optional(),
|
|
4485
|
+
image_url: z.string().optional(),
|
|
4486
|
+
qr_code: z.string().max(200).optional(),
|
|
4487
|
+
from_feature_id: z.string().uuid().optional(),
|
|
4488
|
+
to_feature_id: z.string().uuid().optional(),
|
|
4489
|
+
flow_direction: z.enum(FLOW_DIRECTIONS).optional(),
|
|
4490
|
+
asset_type_id: z.string().uuid().optional(),
|
|
4491
|
+
manufacturer_id: z.string().uuid().optional(),
|
|
4492
|
+
system_class_id: z.string().uuid().optional(),
|
|
4493
|
+
system_group_id: z.string().uuid().optional(),
|
|
4494
|
+
system_id: z.string().uuid().optional(),
|
|
4495
|
+
model: z.string().max(200).optional(),
|
|
4496
|
+
serial_number: z.string().max(200).optional(),
|
|
4497
|
+
site_id: z.string().uuid().optional(),
|
|
4498
|
+
building_id: z.string().uuid().optional(),
|
|
4499
|
+
location_id: z.string().uuid().optional(),
|
|
4500
|
+
status_id: z.string().optional(),
|
|
4501
|
+
condition_score: z.number().min(0).max(100).optional(),
|
|
4502
|
+
last_maintenance_date: z.string().optional(),
|
|
4503
|
+
risk_factor: z.string().optional(),
|
|
4504
|
+
consequence_of_failure_score: z.number().min(0).max(100).optional(),
|
|
4505
|
+
likelihood_of_failure_score: z.number().min(0).max(100).optional(),
|
|
4506
|
+
safety_impact: z.string().optional(),
|
|
4507
|
+
service_impact: z.string().optional(),
|
|
4508
|
+
environmental_impact: z.string().optional(),
|
|
4509
|
+
financial_impact: z.string().optional(),
|
|
4510
|
+
regulatory_impact: z.string().optional(),
|
|
4511
|
+
reputation_impact: z.string().optional(),
|
|
4512
|
+
purchase_cost: z.number().min(0).optional(),
|
|
4513
|
+
purchase_date: z.string().optional(),
|
|
4514
|
+
install_date: z.string().optional(),
|
|
4515
|
+
expected_lifetime_years: z.number().min(0).optional(),
|
|
4516
|
+
salvage_value: z.number().min(0).optional(),
|
|
4517
|
+
salvage_value_percentage: z.number().min(0).max(100).optional(),
|
|
4518
|
+
quantity: z.number().min(0).optional(),
|
|
4519
|
+
unit_of_measure: z.string().max(50).optional(),
|
|
4520
|
+
unit_replacement_value: z.number().min(0).optional(),
|
|
4521
|
+
purchase_cost_calculation_method: z
|
|
4522
|
+
.enum(['manual', 'calculated_sqft', 'calculated_unit'])
|
|
4523
|
+
.optional(),
|
|
4524
|
+
material: z.string().max(200).optional(),
|
|
4525
|
+
diameter_mm: z.number().min(0).optional(),
|
|
4526
|
+
width_m: z.number().min(0).optional(),
|
|
4527
|
+
lanes: z.number().int().min(0).optional(),
|
|
4528
|
+
depth_m: z.number().min(0).optional(),
|
|
4529
|
+
from_invert_m: z.number().optional(),
|
|
4530
|
+
to_invert_m: z.number().optional(),
|
|
4531
|
+
positional_accuracy_class: z.enum(POSITIONAL_ACCURACY).optional(),
|
|
4532
|
+
data_source: z.string().max(200).optional(),
|
|
4533
|
+
}, async ({ id, ...rest }) => {
|
|
4534
|
+
try {
|
|
4535
|
+
const result = await client.update('infrastructure-assets', id, buildBody(rest));
|
|
4536
|
+
return formatResult(result);
|
|
4537
|
+
}
|
|
4538
|
+
catch (err) {
|
|
4539
|
+
return formatError(err);
|
|
4540
|
+
}
|
|
4541
|
+
});
|
|
4542
|
+
server.tool('delete_infrastructure_asset', 'Soft-delete an infrastructure asset (feature) by ID (sets deleted_at). Requires infrastructure_assets:write scope.', { id: z.string().uuid().describe('Infrastructure asset ID') }, async ({ id }) => {
|
|
4543
|
+
try {
|
|
4544
|
+
const result = await client.remove('infrastructure-assets', id);
|
|
4545
|
+
return formatResult(result);
|
|
4546
|
+
}
|
|
4547
|
+
catch (err) {
|
|
4548
|
+
return formatError(err);
|
|
4549
|
+
}
|
|
4550
|
+
});
|
|
4551
|
+
// ============================================================
|
|
4552
|
+
// Infrastructure Asset Inspections (scope: infrastructure_asset_inspections)
|
|
4553
|
+
// ============================================================
|
|
4554
|
+
server.tool('create_infrastructure_asset_inspection', 'Record an inspection against an infrastructure feature. Requires infrastructure_asset_inspections:write scope.', {
|
|
4555
|
+
feature_id: z.string().uuid().describe('Infrastructure asset (feature) ID (required)'),
|
|
4556
|
+
inspection_date: z.string().describe('Inspection date (YYYY-MM-DD, required)'),
|
|
4557
|
+
inspector_id: z.string().optional().describe('Inspector user ID'),
|
|
4558
|
+
method: z.string().max(100).optional().describe('Inspection method (e.g. CCTV, visual)'),
|
|
4559
|
+
condition_score: z.number().min(0).max(100).optional().describe('Condition score (0-100)'),
|
|
4560
|
+
defects: z.record(z.unknown()).optional().describe('Defect observations (JSON)'),
|
|
4561
|
+
notes: z.string().optional().describe('Free-form notes'),
|
|
4562
|
+
attachments: z.array(z.string()).optional().describe('Attachment URLs/paths'),
|
|
4563
|
+
}, async (params) => {
|
|
4564
|
+
try {
|
|
4565
|
+
const result = await client.create('infrastructure-asset-inspections', buildBody(params));
|
|
4566
|
+
return formatResult(result);
|
|
4567
|
+
}
|
|
4568
|
+
catch (err) {
|
|
4569
|
+
return formatError(err);
|
|
4570
|
+
}
|
|
4571
|
+
});
|
|
4572
|
+
server.tool('update_infrastructure_asset_inspection', 'Update an existing infrastructure asset inspection by ID. Requires infrastructure_asset_inspections:write scope.', {
|
|
4573
|
+
id: z.string().uuid().describe('Inspection ID'),
|
|
4574
|
+
inspection_date: z.string().optional().describe('Inspection date (YYYY-MM-DD)'),
|
|
4575
|
+
inspector_id: z.string().optional().describe('Inspector user ID'),
|
|
4576
|
+
method: z.string().max(100).optional().describe('Inspection method'),
|
|
4577
|
+
condition_score: z.number().min(0).max(100).optional().describe('Condition score'),
|
|
4578
|
+
defects: z.record(z.unknown()).optional().describe('Defect observations'),
|
|
4579
|
+
notes: z.string().optional().describe('Notes'),
|
|
4580
|
+
attachments: z.array(z.string()).optional().describe('Attachment URLs/paths'),
|
|
4581
|
+
}, async ({ id, ...rest }) => {
|
|
4582
|
+
try {
|
|
4583
|
+
const result = await client.update('infrastructure-asset-inspections', id, buildBody(rest));
|
|
4584
|
+
return formatResult(result);
|
|
4585
|
+
}
|
|
4586
|
+
catch (err) {
|
|
4587
|
+
return formatError(err);
|
|
4588
|
+
}
|
|
4589
|
+
});
|
|
4590
|
+
server.tool('delete_infrastructure_asset_inspection', 'Soft-delete an infrastructure asset inspection by ID. Requires infrastructure_asset_inspections:write scope.', { id: z.string().uuid().describe('Inspection ID') }, async ({ id }) => {
|
|
4591
|
+
try {
|
|
4592
|
+
const result = await client.remove('infrastructure-asset-inspections', id);
|
|
4593
|
+
return formatResult(result);
|
|
4594
|
+
}
|
|
4595
|
+
catch (err) {
|
|
4596
|
+
return formatError(err);
|
|
4597
|
+
}
|
|
4598
|
+
});
|
|
4599
|
+
// ============================================================
|
|
4600
|
+
// Infrastructure Asset Costs (scope: infrastructure_asset_costs)
|
|
4601
|
+
// ============================================================
|
|
4602
|
+
const INFRA_COST_CATEGORIES = [
|
|
4603
|
+
'Repair',
|
|
4604
|
+
'PM',
|
|
4605
|
+
'Operation',
|
|
4606
|
+
'Replacement',
|
|
4607
|
+
'Decommission',
|
|
4608
|
+
];
|
|
4609
|
+
server.tool('create_infrastructure_asset_cost', 'Record a cost against an infrastructure feature. work_order_number is derived server-side from work_order_id; do not set it. Requires infrastructure_asset_costs:write scope.', {
|
|
4610
|
+
feature_id: z.string().uuid().describe('Infrastructure feature ID (required)'),
|
|
4611
|
+
category: z.enum(INFRA_COST_CATEGORIES).describe('Cost category (required)'),
|
|
4612
|
+
amount: z.number().min(0).describe('Cost amount (required)'),
|
|
4613
|
+
cost_date: z.string().describe('Cost date (YYYY-MM-DD, required)'),
|
|
4614
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number'),
|
|
4615
|
+
po_number: z.string().max(200).optional().describe('PO number'),
|
|
4616
|
+
work_order_id: z.string().uuid().optional().describe('Linked work order ID'),
|
|
4617
|
+
}, async (params) => {
|
|
4618
|
+
try {
|
|
4619
|
+
const result = await client.create('infrastructure-asset-costs', buildBody(params));
|
|
4620
|
+
return formatResult(result);
|
|
4621
|
+
}
|
|
4622
|
+
catch (err) {
|
|
4623
|
+
return formatError(err);
|
|
4624
|
+
}
|
|
4625
|
+
});
|
|
4626
|
+
server.tool('update_infrastructure_asset_cost', 'Update an infrastructure asset cost by ID. Requires infrastructure_asset_costs:write scope.', {
|
|
4627
|
+
id: z.string().uuid().describe('Cost ID'),
|
|
4628
|
+
category: z.enum(INFRA_COST_CATEGORIES).optional().describe('Cost category'),
|
|
4629
|
+
amount: z.number().min(0).optional().describe('Cost amount'),
|
|
4630
|
+
cost_date: z.string().optional().describe('Cost date (YYYY-MM-DD)'),
|
|
4631
|
+
invoice_number: z.string().max(200).optional().describe('Invoice number'),
|
|
4632
|
+
po_number: z.string().max(200).optional().describe('PO number'),
|
|
4633
|
+
work_order_id: z.string().uuid().optional().describe('Linked work order ID'),
|
|
4634
|
+
}, async ({ id, ...rest }) => {
|
|
4635
|
+
try {
|
|
4636
|
+
const result = await client.update('infrastructure-asset-costs', id, buildBody(rest));
|
|
4637
|
+
return formatResult(result);
|
|
4638
|
+
}
|
|
4639
|
+
catch (err) {
|
|
4640
|
+
return formatError(err);
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4643
|
+
server.tool('delete_infrastructure_asset_cost', 'Delete an infrastructure asset cost by ID. Requires infrastructure_asset_costs:write scope.', { id: z.string().uuid().describe('Cost ID') }, async ({ id }) => {
|
|
4644
|
+
try {
|
|
4645
|
+
const result = await client.remove('infrastructure-asset-costs', id);
|
|
4646
|
+
return formatResult(result);
|
|
4647
|
+
}
|
|
4648
|
+
catch (err) {
|
|
4649
|
+
return formatError(err);
|
|
4650
|
+
}
|
|
4651
|
+
});
|
|
4652
|
+
// ============================================================
|
|
4653
|
+
// Infrastructure Asset Parts (scope: infrastructure_asset_parts)
|
|
4654
|
+
// ============================================================
|
|
4655
|
+
server.tool('create_infrastructure_asset_part', 'Associate a part with an infrastructure feature. The (feature_id, part_id) pair must be unique — a duplicate returns 409. Requires infrastructure_asset_parts:write scope.', {
|
|
4656
|
+
feature_id: z.string().uuid().describe('Infrastructure feature ID (required)'),
|
|
4657
|
+
part_id: z.string().uuid().describe('Part ID (required; resolve via list_parts)'),
|
|
4658
|
+
quantity: z.number().min(0).optional().describe('Design/installed quantity (default 1)'),
|
|
4659
|
+
}, async (params) => {
|
|
4660
|
+
try {
|
|
4661
|
+
const result = await client.create('infrastructure-asset-parts', buildBody(params));
|
|
4662
|
+
return formatResult(result);
|
|
4663
|
+
}
|
|
4664
|
+
catch (err) {
|
|
4665
|
+
return formatError(err);
|
|
4666
|
+
}
|
|
4667
|
+
});
|
|
4668
|
+
server.tool('update_infrastructure_asset_part', 'Update an infrastructure asset part association by ID. Requires infrastructure_asset_parts:write scope.', {
|
|
4669
|
+
id: z.string().uuid().describe('Association ID'),
|
|
4670
|
+
feature_id: z.string().uuid().optional().describe('Infrastructure feature ID'),
|
|
4671
|
+
part_id: z.string().uuid().optional().describe('Part ID'),
|
|
4672
|
+
quantity: z.number().min(0).optional().describe('Design/installed quantity'),
|
|
4673
|
+
}, async ({ id, ...rest }) => {
|
|
4674
|
+
try {
|
|
4675
|
+
const result = await client.update('infrastructure-asset-parts', id, buildBody(rest));
|
|
4676
|
+
return formatResult(result);
|
|
4677
|
+
}
|
|
4678
|
+
catch (err) {
|
|
4679
|
+
return formatError(err);
|
|
4680
|
+
}
|
|
4681
|
+
});
|
|
4682
|
+
server.tool('delete_infrastructure_asset_part', 'Delete an infrastructure asset part association by ID. Requires infrastructure_asset_parts:write scope.', { id: z.string().uuid().describe('Association ID') }, async ({ id }) => {
|
|
4683
|
+
try {
|
|
4684
|
+
const result = await client.remove('infrastructure-asset-parts', id);
|
|
4685
|
+
return formatResult(result);
|
|
4686
|
+
}
|
|
4687
|
+
catch (err) {
|
|
4688
|
+
return formatError(err);
|
|
4689
|
+
}
|
|
4690
|
+
});
|
|
4691
|
+
// ============================================================
|
|
4692
|
+
// Infrastructure Asset Documents (scope: infrastructure_asset_documents)
|
|
4693
|
+
// ============================================================
|
|
4694
|
+
server.tool('create_infrastructure_asset_document', 'Attach document metadata to an infrastructure feature. Upload the file bytes first via create_upload_url, then pass the returned file_path here. Requires infrastructure_asset_documents:write scope.', {
|
|
4695
|
+
feature_id: z.string().uuid().describe('Infrastructure feature ID (required)'),
|
|
4696
|
+
name: z.string().max(500).describe('Document name (required)'),
|
|
4697
|
+
file_path: z.string().max(2000).describe('Storage path from create_upload_url (required)'),
|
|
4698
|
+
description: z.string().optional().describe('Description'),
|
|
4699
|
+
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
4700
|
+
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
4701
|
+
category: z.string().max(100).optional().describe('Document category'),
|
|
4702
|
+
}, async (params) => {
|
|
4703
|
+
try {
|
|
4704
|
+
const result = await client.create('infrastructure-asset-documents', buildBody(params));
|
|
4705
|
+
return formatResult(result);
|
|
4706
|
+
}
|
|
4707
|
+
catch (err) {
|
|
4708
|
+
return formatError(err);
|
|
4709
|
+
}
|
|
4710
|
+
});
|
|
4711
|
+
server.tool('update_infrastructure_asset_document', 'Update infrastructure asset document metadata by ID. Requires infrastructure_asset_documents:write scope.', {
|
|
4712
|
+
id: z.string().uuid().describe('Document ID'),
|
|
4713
|
+
name: z.string().max(500).optional().describe('Document name'),
|
|
4714
|
+
file_path: z.string().max(2000).optional().describe('Storage path'),
|
|
4715
|
+
description: z.string().optional().describe('Description'),
|
|
4716
|
+
file_type: z.string().max(200).optional().describe('MIME type'),
|
|
4717
|
+
file_size: z.number().min(0).optional().describe('File size in bytes'),
|
|
4718
|
+
category: z.string().max(100).optional().describe('Document category'),
|
|
4719
|
+
}, async ({ id, ...rest }) => {
|
|
4720
|
+
try {
|
|
4721
|
+
const result = await client.update('infrastructure-asset-documents', id, buildBody(rest));
|
|
4722
|
+
return formatResult(result);
|
|
4723
|
+
}
|
|
4724
|
+
catch (err) {
|
|
4725
|
+
return formatError(err);
|
|
4726
|
+
}
|
|
4727
|
+
});
|
|
4728
|
+
server.tool('delete_infrastructure_asset_document', 'Delete an infrastructure asset document by ID. Requires infrastructure_asset_documents:write scope.', { id: z.string().uuid().describe('Document ID') }, async ({ id }) => {
|
|
4729
|
+
try {
|
|
4730
|
+
const result = await client.remove('infrastructure-asset-documents', id);
|
|
4731
|
+
return formatResult(result);
|
|
4732
|
+
}
|
|
4733
|
+
catch (err) {
|
|
4734
|
+
return formatError(err);
|
|
4735
|
+
}
|
|
4736
|
+
});
|
|
4737
|
+
// ============================================================
|
|
4738
|
+
// Infrastructure Asset Comments (scope: infrastructure_asset_comments)
|
|
4739
|
+
// ============================================================
|
|
4740
|
+
server.tool('create_infrastructure_asset_comment', 'Add a comment to an infrastructure feature. The author is attributed to the API key automatically; do not pass user_id. Requires infrastructure_asset_comments:write scope.', {
|
|
4741
|
+
feature_id: z.string().uuid().describe('Infrastructure feature ID (required)'),
|
|
4742
|
+
comment: z.string().max(10000).describe('Comment text (required)'),
|
|
4743
|
+
}, async (params) => {
|
|
4744
|
+
try {
|
|
4745
|
+
const result = await client.create('infrastructure-asset-comments', buildBody(params));
|
|
4746
|
+
return formatResult(result);
|
|
4747
|
+
}
|
|
4748
|
+
catch (err) {
|
|
4749
|
+
return formatError(err);
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
server.tool('update_infrastructure_asset_comment', 'Update an infrastructure asset comment by ID. Requires infrastructure_asset_comments:write scope.', {
|
|
4753
|
+
id: z.string().uuid().describe('Comment ID'),
|
|
4754
|
+
comment: z.string().max(10000).optional().describe('Comment text'),
|
|
4755
|
+
}, async ({ id, ...rest }) => {
|
|
4756
|
+
try {
|
|
4757
|
+
const result = await client.update('infrastructure-asset-comments', id, buildBody(rest));
|
|
4758
|
+
return formatResult(result);
|
|
4759
|
+
}
|
|
4760
|
+
catch (err) {
|
|
4761
|
+
return formatError(err);
|
|
4762
|
+
}
|
|
4763
|
+
});
|
|
4764
|
+
server.tool('delete_infrastructure_asset_comment', 'Delete an infrastructure asset comment by ID. Requires infrastructure_asset_comments:write scope.', { id: z.string().uuid().describe('Comment ID') }, async ({ id }) => {
|
|
4765
|
+
try {
|
|
4766
|
+
const result = await client.remove('infrastructure-asset-comments', id);
|
|
4767
|
+
return formatResult(result);
|
|
4768
|
+
}
|
|
4769
|
+
catch (err) {
|
|
4770
|
+
return formatError(err);
|
|
4771
|
+
}
|
|
4772
|
+
});
|
|
4773
|
+
// ============================================================
|
|
4774
|
+
// Infrastructure Zones (scope: infrastructure_zones)
|
|
4775
|
+
// ============================================================
|
|
4776
|
+
const INFRA_ZONE_KINDS = [
|
|
4777
|
+
'pressure_zone',
|
|
4778
|
+
'dma',
|
|
4779
|
+
'sewershed',
|
|
4780
|
+
'storm_catchment',
|
|
4781
|
+
'maintenance_district',
|
|
4782
|
+
];
|
|
4783
|
+
const geoJsonPolygon = z
|
|
4784
|
+
.object({
|
|
4785
|
+
type: z.literal('Polygon'),
|
|
4786
|
+
coordinates: z.array(z.array(z.tuple([z.number(), z.number()]))),
|
|
4787
|
+
})
|
|
4788
|
+
.describe('GeoJSON Polygon (EPSG:4326). Each ring is closed; coordinates are [lon, lat].');
|
|
4789
|
+
server.tool('create_infrastructure_zone', 'Create an operational hydraulic boundary (pressure zone, DMA, sewershed, etc.). boundary is a GeoJSON Polygon (single polygon only; MultiPolygon is rejected). (network_id, name) must be unique. Requires infrastructure_zones:write scope.', {
|
|
4790
|
+
network_id: z.string().uuid().describe('Infrastructure network ID (required)'),
|
|
4791
|
+
kind: z.enum(INFRA_ZONE_KINDS).describe('Zone kind (required)'),
|
|
4792
|
+
name: z.string().max(200).describe('Zone name (required)'),
|
|
4793
|
+
boundary: geoJsonPolygon,
|
|
4794
|
+
code: z.string().max(50).optional().describe('Optional short code (e.g. "PZ-04")'),
|
|
4795
|
+
notes: z.string().optional().describe('Free-form notes'),
|
|
4796
|
+
}, async (params) => {
|
|
4797
|
+
try {
|
|
4798
|
+
const result = await client.create('infrastructure-zones', buildBody(params));
|
|
4799
|
+
return formatResult(result);
|
|
4800
|
+
}
|
|
4801
|
+
catch (err) {
|
|
4802
|
+
return formatError(err);
|
|
4803
|
+
}
|
|
4804
|
+
});
|
|
4805
|
+
server.tool('update_infrastructure_zone', 'Update an infrastructure zone by ID. Pass boundary as a GeoJSON Polygon to replace the geometry. Requires infrastructure_zones:write scope.', {
|
|
4806
|
+
id: z.string().uuid().describe('Zone ID'),
|
|
4807
|
+
network_id: z.string().uuid().optional().describe('Infrastructure network ID'),
|
|
4808
|
+
kind: z.enum(INFRA_ZONE_KINDS).optional().describe('Zone kind'),
|
|
4809
|
+
name: z.string().max(200).optional().describe('Zone name'),
|
|
4810
|
+
boundary: geoJsonPolygon.optional(),
|
|
4811
|
+
code: z.string().max(50).optional().describe('Optional short code'),
|
|
4812
|
+
notes: z.string().optional().describe('Free-form notes'),
|
|
4813
|
+
}, async ({ id, ...rest }) => {
|
|
4814
|
+
try {
|
|
4815
|
+
const result = await client.update('infrastructure-zones', id, buildBody(rest));
|
|
4816
|
+
return formatResult(result);
|
|
4817
|
+
}
|
|
4818
|
+
catch (err) {
|
|
4819
|
+
return formatError(err);
|
|
4820
|
+
}
|
|
4821
|
+
});
|
|
4822
|
+
server.tool('delete_infrastructure_zone', 'Delete an infrastructure zone by ID. Requires infrastructure_zones:write scope.', { id: z.string().uuid().describe('Zone ID') }, async ({ id }) => {
|
|
4823
|
+
try {
|
|
4824
|
+
const result = await client.remove('infrastructure-zones', id);
|
|
4825
|
+
return formatResult(result);
|
|
4826
|
+
}
|
|
4827
|
+
catch (err) {
|
|
4828
|
+
return formatError(err);
|
|
4829
|
+
}
|
|
4830
|
+
});
|
|
4831
|
+
// ============================================================
|
|
4832
|
+
// Project ↔ Infrastructure Asset links (scope: project_infrastructure_assets)
|
|
4833
|
+
// ============================================================
|
|
4834
|
+
server.tool('create_project_infrastructure_asset', 'Link a project to an infrastructure feature. The (project_id, feature_id) pair must be unique — a duplicate returns 409. Requires project_infrastructure_assets:write scope.', {
|
|
4835
|
+
project_id: z.string().uuid().describe('Project ID (required)'),
|
|
4836
|
+
feature_id: z.string().uuid().describe('Infrastructure feature ID (required)'),
|
|
4837
|
+
notes: z.string().optional().describe('Free-form notes'),
|
|
4838
|
+
}, async (params) => {
|
|
4839
|
+
try {
|
|
4840
|
+
const result = await client.create('project-infrastructure-assets', buildBody(params));
|
|
4841
|
+
return formatResult(result);
|
|
4842
|
+
}
|
|
4843
|
+
catch (err) {
|
|
4844
|
+
return formatError(err);
|
|
4845
|
+
}
|
|
4846
|
+
});
|
|
4847
|
+
server.tool('update_project_infrastructure_asset', 'Update a project ↔ infrastructure feature link by ID. Requires project_infrastructure_assets:write scope.', {
|
|
4848
|
+
id: z.string().uuid().describe('Link ID'),
|
|
4849
|
+
project_id: z.string().uuid().optional().describe('Project ID'),
|
|
4850
|
+
feature_id: z.string().uuid().optional().describe('Infrastructure feature ID'),
|
|
4851
|
+
notes: z.string().optional().describe('Free-form notes'),
|
|
4852
|
+
}, async ({ id, ...rest }) => {
|
|
4853
|
+
try {
|
|
4854
|
+
const result = await client.update('project-infrastructure-assets', id, buildBody(rest));
|
|
4855
|
+
return formatResult(result);
|
|
4856
|
+
}
|
|
4857
|
+
catch (err) {
|
|
4858
|
+
return formatError(err);
|
|
4859
|
+
}
|
|
4860
|
+
});
|
|
4861
|
+
server.tool('delete_project_infrastructure_asset', 'Delete a project ↔ infrastructure feature link by ID. Requires project_infrastructure_assets:write scope.', { id: z.string().uuid().describe('Link ID') }, async ({ id }) => {
|
|
4862
|
+
try {
|
|
4863
|
+
const result = await client.remove('project-infrastructure-assets', id);
|
|
4864
|
+
return formatResult(result);
|
|
4865
|
+
}
|
|
4866
|
+
catch (err) {
|
|
4867
|
+
return formatError(err);
|
|
4868
|
+
}
|
|
4869
|
+
});
|
|
4870
|
+
// ============================================================
|
|
4283
4871
|
// Bulk Operations
|
|
4284
4872
|
// ============================================================
|
|
4285
4873
|
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.', {
|