@assetlab/mcp-server 2.2.0 โ†’ 2.3.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 CHANGED
@@ -1,174 +1,242 @@
1
- # @assetlab/mcp-server
1
+ # AssetLab MCP Server
2
2
 
3
- Connect your AI assistant to your [AssetLab](https://assetlab.ca) account via the [Model Context Protocol](https://modelcontextprotocol.io). Ask questions about your assets, work orders, PM schedules, and more โ€” in plain language. Create and update records hands-free.
3
+ [![npm version](https://img.shields.io/npm/v/%40assetlab%2Fmcp-server?color=cb3837&logo=npm)](https://www.npmjs.com/package/@assetlab/mcp-server)
4
+ [![weekly downloads](https://img.shields.io/npm/dw/%40assetlab%2Fmcp-server?color=blue)](https://www.npmjs.com/package/@assetlab/mcp-server)
5
+ [![types](https://img.shields.io/badge/types-included-3178c6?logo=typescript&logoColor=white)](https://www.npmjs.com/package/@assetlab/mcp-server)
6
+ [![license](https://img.shields.io/npm/l/%40assetlab%2Fmcp-server?color=green)](https://opensource.org/licenses/MIT)
4
7
 
5
- Works with **Claude**, **ChatGPT**, and any MCP-compatible client.
8
+ **Talk to your assets.** Connect Claude, ChatGPT, or any MCP-compatible client to your
9
+ [AssetLab](https://assetlab.ca) account through the
10
+ [Model Context Protocol](https://modelcontextprotocol.io) - ask about work orders, PM
11
+ schedules, capital plans, and infrastructure networks in plain language, and create or
12
+ update records hands-free.
6
13
 
7
- ## Prerequisites
14
+ > ๐Ÿงฐ **428 tools** across **100+ resources** ยท ๐Ÿ” scoped, tenant-bound, audited ยท
15
+ > โœ๏ธ read *and* write ยท ๐ŸŒ hosted or local
8
16
 
9
- - An AssetLab account with an API key (create one in **Settings โ†’ API Keys**)
17
+ ---
10
18
 
11
- ## Setup
19
+ ## ๐Ÿš€ Quick start
12
20
 
13
- ### Claude.ai
21
+ ### Claude.ai (hosted - easiest)
14
22
 
15
23
  1. Create an API key in **AssetLab โ†’ Settings โ†’ API Keys**
16
- 2. In Claude.ai, go to **Settings โ†’ Connectors โ†’ Add connector**
24
+ 2. In Claude.ai: **Settings โ†’ Connectors โ†’ Add connector**
17
25
  3. Paste the connector URL: `https://mcp.assetlab.ca`
18
26
  4. When prompted for auth, paste your API key (`al_live_...`)
19
27
 
20
28
  ### ChatGPT
21
29
 
22
30
  1. Create an API key in **AssetLab โ†’ Settings โ†’ API Keys**
23
- 2. In ChatGPT, go to **Settings โ†’ Apps & Connectors โ†’ Add new connector**
24
- 3. Enter a name (e.g. `AssetLab`) and the server URL: `https://mcp.assetlab.ca`
25
- 4. Set authentication to **OAuth**
26
- 5. Click **Create** โ€” ChatGPT will auto-discover the OAuth endpoints
27
- 6. When redirected to the AssetLab authorization page, paste your API key (`al_live_...`)
28
- 7. After authorization, AssetLab tools will appear in your ChatGPT conversations
29
-
30
- ## Example prompts
31
-
32
- - *"Show me all overdue work orders"*
33
- - *"List critical assets at Site A"*
34
- - *"What PM schedules are due this month?"*
35
- - *"Create a work order for the broken pump in Building 3"*
36
- - *"Add a new vendor for electrical services"*
37
- - *"Update the priority on WO-1234 to urgent"*
38
- - *"Which projects are at risk or delayed?"*
39
- - *"What's the total cost history for Asset X?"*
40
- - *"Create a part for 10-inch butterfly valves"*
41
- - *"List all replacement plans due in 2027"*
42
-
43
- ## Available tools
44
-
45
- ### Read tools
46
-
47
- | Resource | List | Get |
48
- |----------|------|-----|
49
- | Assets | `list_assets` | `get_asset` |
50
- | Work Orders | `list_work_orders` | `get_work_order` |
51
- | Work Requests | `list_work_requests` | `get_work_request` |
52
- | Sites | `list_sites` | `get_site` |
53
- | Buildings | `list_buildings` | โ€” |
54
- | Locations | `list_locations` | โ€” |
55
- | Systems | `list_systems` | โ€” |
56
- | System Groups | `list_system_groups` | โ€” |
57
- | System Classes | `list_system_classes` | โ€” |
58
- | PM Schedules | `list_pm_schedules` | `get_pm_schedule` |
59
- | PM Templates | `list_pm_templates` | โ€” |
60
- | Projects | `list_projects` | `get_project` |
61
- | Contracts | `list_contracts` | โ€” |
62
- | Compliance Items | `list_compliance_items` | `get_compliance_item` |
63
- | Compliance Records | `list_compliance_records` | `get_compliance_record` |
64
- | Dashboard | โ€” | `get_dashboard_summary` |
65
- | Dashboard Snapshots | `list_dashboard_snapshots` | `get_dashboard_snapshot` |
66
- | Vendors | `list_vendors` | `get_vendor` |
67
- | Invoices | `list_invoices` | `get_invoice` |
68
- | Purchase Orders | `list_purchase_orders` | `get_purchase_order` |
69
- | Expenses | `list_expenses` | `get_expense` |
70
- | Change Orders | `list_change_orders` | `get_change_order` |
71
- | Budgets | `list_budgets` | `get_budget` |
72
- | Parts | `list_parts` | `get_part` |
73
- | Part Categories | `list_part_categories` | `get_part_category` |
74
- | Asset Comments | `list_asset_comments` | `get_asset_comment` |
75
- | Asset Costs | `list_asset_costs` | `get_asset_cost` |
76
- | Replacement Plans | `list_asset_replacement_plans` | `get_asset_replacement_plan` |
77
- | Risk History | `list_asset_risk_history` | `get_asset_risk_history_entry` |
78
- | WO Comments | `list_work_order_comments` | `get_work_order_comment` |
79
- | Project Tasks | `list_project_tasks` | `get_project_task` |
80
- | Project Milestones | `list_project_milestones` | `get_project_milestone` |
81
- | Project Phases | `list_project_phases` | `get_project_phase` |
82
- | Project Budget Items | `list_project_budget_items` | `get_project_budget_item` |
83
- | Project Time Entries | `list_project_time_entries` | `get_project_time_entry` |
84
- | Project Comments | `list_project_comments` | `get_project_comment` |
85
- | Project Document Folder Templates | `list_project_document_folder_templates` | `get_project_document_folder_template` |
86
- | Site FCI History | `list_site_fci_history` | `get_site_fci_history_entry` |
87
- | Vendor Site Assignments | `list_vendor_site_assignments` | `get_vendor_site_assignment` |
88
- | Contract Sites | `list_contract_sites` | โ€” |
89
- | Custom Field Definitions | `list_custom_field_definitions` | `get_custom_field_definition` |
90
- | Custom Field Values | `list_custom_field_values` | `get_custom_field_value` |
91
- | Asset Types | `list_asset_types` | โ€” |
92
- | Work Categories | `list_work_categories` | โ€” |
93
- | Manufacturers | `list_manufacturers` | `get_manufacturer` |
94
- | Building Types | `list_building_types` | โ€” |
95
- | Location Types | `list_location_types` | โ€” |
96
- | Project Phase Categories | `list_project_phase_categories` | โ€” |
97
- | Cost Categories | `list_cost_categories` | โ€” |
98
- | Infrastructure Asset Classes | `list_infrastructure_asset_classes` | `get_infrastructure_asset_class` |
99
- | Infrastructure Networks | `list_infrastructure_networks` | `get_infrastructure_network` |
100
- | Infrastructure Features | `list_infrastructure_assets` | `get_infrastructure_asset` |
101
- | Infrastructure Inspections | `list_infrastructure_asset_inspections` | `get_infrastructure_asset_inspection` |
102
-
103
- ### Write tools
104
-
105
- | Resource | Create | Update | Delete |
106
- |----------|--------|--------|--------|
107
- | Assets | `create_asset` | `update_asset` | `delete_asset` |
108
- | Work Orders | `create_work_order` | `update_work_order` | `delete_work_order` |
109
- | Work Requests | `create_work_request` | `update_work_request` | `delete_work_request` |
110
- | Sites | `create_site` | `update_site` | `delete_site` |
111
- | Buildings | `create_building` | `update_building` | `delete_building` |
112
- | Locations | `create_location` | `update_location` | `delete_location` |
113
- | Systems | `create_system` | `update_system` | `delete_system` |
114
- | PM Schedules | `create_pm_schedule` | `update_pm_schedule` | `delete_pm_schedule` |
115
- | Projects | `create_project` | `update_project` | `delete_project` |
116
- | Contracts | `create_contract` | `update_contract` | `delete_contract` |
117
- | Vendors | `create_vendor` | `update_vendor` | `delete_vendor` |
118
- | Invoices | `create_invoice` | `update_invoice` | `delete_invoice` |
119
- | Purchase Orders | `create_purchase_order` | `update_purchase_order` | `delete_purchase_order` |
120
- | Expenses | `create_expense` | `update_expense` | `delete_expense` |
121
- | Change Orders | `create_change_order` | `update_change_order` | `delete_change_order` |
122
- | Budgets | `create_budget` | `update_budget` | `delete_budget` |
123
- | Parts | `create_part` | `update_part` | `delete_part` |
124
- | Part Categories | `create_part_category` | `update_part_category` | `delete_part_category` |
125
- | Asset Comments | `create_asset_comment` | `update_asset_comment` | `delete_asset_comment` |
126
- | Asset Costs | `create_asset_cost` | `update_asset_cost` | `delete_asset_cost` |
127
- | Replacement Plans | `create_asset_replacement_plan` | `update_asset_replacement_plan` | `delete_asset_replacement_plan` |
128
- | WO Comments | `create_work_order_comment` | `update_work_order_comment` | `delete_work_order_comment` |
129
- | Project Tasks | `create_project_task` | `update_project_task` | `delete_project_task` |
130
- | Project Milestones | `create_project_milestone` | `update_project_milestone` | `delete_project_milestone` |
131
- | Project Phases | `create_project_phase` | `update_project_phase` | `delete_project_phase` |
132
- | Project Budget Items | `create_project_budget_item` | `update_project_budget_item` | `delete_project_budget_item` |
133
- | Project Time Entries | `create_project_time_entry` | `update_project_time_entry` | `delete_project_time_entry` |
134
- | Project Comments | `create_project_comment` | `update_project_comment` | `delete_project_comment` |
135
- | Project Document Folder Templates | `create_project_document_folder_template` | `update_project_document_folder_template` | `delete_project_document_folder_template` |
136
- | Vendor Site Assignments | `create_vendor_site_assignment` | โ€” | `delete_vendor_site_assignment` |
137
- | Contract Sites | `create_contract_site` | โ€” | `delete_contract_site` |
138
- | Custom Field Definitions | `create_custom_field_definition` | `update_custom_field_definition` | `delete_custom_field_definition` |
139
- | Custom Field Values | `create_custom_field_value` | `update_custom_field_value` | `delete_custom_field_value` |
140
- | Asset Types | `create_asset_type` | `update_asset_type` | `delete_asset_type` |
141
- | Work Categories | `create_work_category` | `update_work_category` | `delete_work_category` |
142
- | Manufacturers | `create_manufacturer` | `update_manufacturer` | `delete_manufacturer` |
143
- | Building Types | `create_building_type` | `update_building_type` | `delete_building_type` |
144
- | Location Types | `create_location_type` | `update_location_type` | `delete_location_type` |
145
- | Project Phase Categories | `create_project_phase_category` | `update_project_phase_category` | `delete_project_phase_category` |
146
- | Cost Categories | `create_cost_category` | `update_cost_category` | `delete_cost_category` |
147
- | Infrastructure Asset Classes | `create_infrastructure_asset_class` | `update_infrastructure_asset_class` | `delete_infrastructure_asset_class` |
148
- | Infrastructure Networks | `create_infrastructure_network` | `update_infrastructure_network` | `delete_infrastructure_network` |
149
- | Infrastructure Features | `create_infrastructure_asset` | `update_infrastructure_asset` | `delete_infrastructure_asset` |
150
- | Infrastructure Inspections | `create_infrastructure_asset_inspection` | `update_infrastructure_asset_inspection` | `delete_infrastructure_asset_inspection` |
151
-
152
- ## Scopes
153
-
154
- API keys use scopes to control what Claude can access. Configure per-resource read and write access in **Settings โ†’ API Keys**.
31
+ 2. In ChatGPT: **Settings โ†’ Apps & Connectors โ†’ Add new connector**
32
+ 3. Name it (e.g. `AssetLab`) and enter the server URL: `https://mcp.assetlab.ca`
33
+ 4. Set authentication to **OAuth** and click **Create** - the OAuth endpoints are
34
+ auto-discovered
35
+ 5. On the AssetLab authorization page, paste your API key (`al_live_...`)
36
+
37
+ ### Claude Desktop / Claude Code (local, stdio)
38
+
39
+ This package runs as a local MCP server over stdio - nothing is exposed on the network.
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "assetlab": {
45
+ "command": "npx",
46
+ "args": ["-y", "@assetlab/mcp-server"],
47
+ "env": {
48
+ "ASSETLAB_API_KEY": "al_live_...",
49
+ "ASSETLAB_API_URL": "https://<your-project>.supabase.co/functions/v1/api-gateway"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ Your AssetLab administrator can provide the API URL for your organization.
57
+
58
+ ---
59
+
60
+ ## ๐Ÿ’ฌ What can you ask?
61
+
62
+ | You say | It does |
63
+ |---|---|
64
+ | *"Show me all overdue work orders"* | Lists them, sorted, with links back to AssetLab |
65
+ | *"Create a work order for the broken pump in Building 3"* | Looks up the building and asset, then creates it |
66
+ | *"Which projects are at risk or delayed?"* | Scans project status and risks |
67
+ | *"What's the total cost history for Asset X?"* | Aggregates every cost record |
68
+ | *"List watermains over 500 mm in poor condition"* | Filters infrastructure features by diameter and condition |
69
+ | *"Add a crack-sealing event to the sidewalk strategy at $4 per square metre"* | Maintains your lifecycle strategies ๐Ÿ†• |
70
+ | *"What PM schedules are due this month?"* | Checks every schedule's next due date |
71
+ | *"Update the priority on WO-1234 to urgent"* | Edits the record (with your write scope) |
72
+
73
+ ---
74
+
75
+ ## ๐Ÿ†• What's new in 2.3
76
+
77
+ **Infrastructure lifecycle events.** Read and maintain lifecycle strategies - the
78
+ condition-triggered interventions ("crack sealing at condition 80-90 adds two years")
79
+ that bend renewal forecasts across the planner, the map, and corridor bundling. Five new
80
+ tools under the `infrastructure_lifecycle_events` scope.
81
+
82
+ ---
83
+
84
+ ## ๐Ÿงฐ Tool catalog
85
+
86
+ Tools follow one naming pattern throughout: `list_*` / `get_*` to read, `create_*` /
87
+ `update_*` / `delete_*` to write. **Read** below means list/get; **Write** means
88
+ create/update/delete (availability varies slightly per resource - the tool descriptions
89
+ are the authority).
90
+
91
+ <details>
92
+ <summary><b>๐Ÿญ Assets &amp; maintenance records</b> (16 resources)</summary>
93
+
94
+ | Resource | Read | Write |
95
+ |---|:-:|:-:|
96
+ | Assets | โœ“ | โœ“ |
97
+ | Asset comments ยท costs ยท documents ยท parts | โœ“ | โœ“ |
98
+ | Asset placements (floorplan pins) | โœ“ | โœ“ |
99
+ | Asset replacement plans | โœ“ | โœ“ |
100
+ | Asset condition assessments | โœ“ | โœ“ |
101
+ | Asset risk history | โœ“ | - |
102
+ | Asset statuses ยท types ยท type groups | โœ“ | โœ“ |
103
+ | Attachments (+ `upload_file`, `create_upload_url`) | โœ“ | โœ“ |
104
+ | Manufacturers | โœ“ | โœ“ |
105
+ | Parts ยท part categories | โœ“ | โœ“ |
106
+
107
+ </details>
108
+
109
+ <details>
110
+ <summary><b>๐Ÿ› ๏ธ Work management</b> (7 resources)</summary>
111
+
112
+ | Resource | Read | Write |
113
+ |---|:-:|:-:|
114
+ | Work orders | โœ“ | โœ“ |
115
+ | Work order comments ยท schedules | โœ“ | โœ“ |
116
+ | Work requests | โœ“ | โœ“ |
117
+ | Work categories | โœ“ | โœ“ |
118
+ | PM schedules | โœ“ | โœ“ |
119
+ | PM templates | โœ“ | โœ“ |
120
+
121
+ </details>
122
+
123
+ <details>
124
+ <summary><b>๐Ÿ“ Sites, buildings &amp; systems</b> (11 resources)</summary>
125
+
126
+ | Resource | Read | Write |
127
+ |---|:-:|:-:|
128
+ | Sites | โœ“ | โœ“ |
129
+ | Site FCI history | โœ“ | - |
130
+ | Buildings ยท building types | โœ“ | โœ“ |
131
+ | Locations ยท location types | โœ“ | โœ“ |
132
+ | Systems ยท system classes ยท system groups | โœ“ | โœ“ |
133
+ | Floorplans ยท floorplan regions | โœ“ | โœ“ |
134
+
135
+ </details>
136
+
137
+ <details>
138
+ <summary><b>๐Ÿ›ฃ๏ธ Infrastructure (linear assets)</b> (11 resources)</summary>
139
+
140
+ | Resource | Read | Write |
141
+ |---|:-:|:-:|
142
+ | Feature classes | โœ“ | โœ“ |
143
+ | Networks | โœ“ | โœ“ |
144
+ | Features (segments &amp; nodes, GeoJSON geometry) | โœ“ | โœ“ |
145
+ | Feature inspections | โœ“ | โœ“ |
146
+ | Lifecycle events (strategies) ๐Ÿ†• | โœ“ | โœ“ |
147
+ | Zones | โœ“ | โœ“ |
148
+ | Feature comments ยท costs ยท documents ยท parts | โœ“ | โœ“ |
149
+ | Feature risk history | โœ“ | - |
150
+
151
+ </details>
152
+
153
+ <details>
154
+ <summary><b>๐Ÿ“Š Projects &amp; capital planning</b> (20+ resources)</summary>
155
+
156
+ | Resource | Read | Write |
157
+ |---|:-:|:-:|
158
+ | Projects | โœ“ | โœ“ |
159
+ | Tasks ยท task dependencies ยท milestones | โœ“ | โœ“ |
160
+ | Phases ยท phase categories | โœ“ | โœ“ |
161
+ | Budget items ยท time entries ยท cost snapshots | โœ“ | โœ“ |
162
+ | Comments ยท updates ยท risks ยท team members | โœ“ | โœ“ |
163
+ | Documents ยท document folder templates | โœ“ | โœ“ |
164
+ | Project links (assets, buildings, sites, locations, systems, infrastructure) | โœ“ | โœ“ |
165
+ | Service areas (+ site and system-class links) | โœ“ | โœ“ |
166
+
167
+ </details>
168
+
169
+ <details>
170
+ <summary><b>๐Ÿ’ฐ Finance &amp; vendors</b> (10 resources)</summary>
171
+
172
+ | Resource | Read | Write |
173
+ |---|:-:|:-:|
174
+ | Budgets | โœ“ | โœ“ |
175
+ | Expenses ยท invoices ยท purchase orders | โœ“ | โœ“ |
176
+ | Change orders | โœ“ | โœ“ |
177
+ | Cost categories | โœ“ | โœ“ |
178
+ | Contracts ยท contract sites ยท contract documents | โœ“ | โœ“ |
179
+ | Vendors ยท vendor site assignments | โœ“ | โœ“ |
180
+
181
+ </details>
182
+
183
+ <details>
184
+ <summary><b>โœ… Compliance, forms &amp; level of service</b> (9 resources)</summary>
185
+
186
+ | Resource | Read | Write |
187
+ |---|:-:|:-:|
188
+ | Compliance items ยท records | โœ“ | โœ“ |
189
+ | Form templates ยท template items | โœ“ | โœ“ |
190
+ | Form responses | โœ“ | โœ“ |
191
+ | Form response answers | โœ“ | - |
192
+ | LoS measures ยท measurements | โœ“ | โœ“ |
193
+ | LoS targets history | โœ“ | - |
194
+
195
+ </details>
196
+
197
+ <details>
198
+ <summary><b>โš™๏ธ Admin &amp; analytics</b> (6 resources)</summary>
199
+
200
+ | Resource | Read | Write |
201
+ |---|:-:|:-:|
202
+ | Users | โœ“ | - |
203
+ | Custom field definitions ยท values | โœ“ | โœ“ |
204
+ | Dashboard summary ยท snapshots | โœ“ | - |
205
+ | Bulk operations (`bulk_create`, `bulk_update`) | - | โœ“ |
206
+
207
+ </details>
208
+
209
+ ---
210
+
211
+ ## ๐Ÿ” Scopes
212
+
213
+ API keys use scopes to control exactly what your assistant can touch. Configure
214
+ per-resource read and write access in **Settings โ†’ API Keys** - scopes are grouped by
215
+ category (Operations, Facilities, Projects, Finance, Infrastructure, ...) with
216
+ bulk-toggle controls.
155
217
 
156
218
  | Scope pattern | Access |
157
- |---------------|--------|
219
+ |---|---|
158
220
  | `resource:read` | List and get records (e.g. `assets:read`, `work_orders:read`) |
159
- | `resource:write` | Create, update, and delete records (e.g. `assets:write`, `parts:write`) |
221
+ | `resource:write` | Create, update, and delete records (e.g. `assets:write`) |
160
222
  | `*:*` | Full access to all resources |
161
223
 
162
- Write scopes require the corresponding read scope to be useful. When creating an API key, scopes are organized by category (Operations, Facilities, Projects, Finance, etc.) with bulk-toggle controls.
224
+ ---
163
225
 
164
- ## Security
226
+ ## ๐Ÿ›ก๏ธ Security
165
227
 
166
- - Connects via OAuth 2.0 + PKCE through `mcp.assetlab.ca`. Your API key is encrypted in transit and never stored by the MCP server.
167
- - Your API key is scoped to your organization only.
168
- - All data access is logged in AssetLab's audit log.
169
- - API keys can be revoked at any time in **Settings โ†’ API Keys**.
170
- - Write operations require explicit write scopes โ€” read-only by default.
228
+ - ๐Ÿ”’ **OAuth 2.0 + PKCE** through `mcp.assetlab.ca`; your API key is encrypted in
229
+ transit and never stored by the MCP server
230
+ - ๐Ÿข **Tenant-bound** - every key is scoped to your organization only, and the server
231
+ ignores any attempt to address another tenant
232
+ - ๐Ÿ“ **Audited** - all data access is logged in AssetLab's audit log
233
+ - ๐Ÿงฏ **Revocable** - revoke a key any time in **Settings โ†’ API Keys**; it dies instantly
234
+ - โœ‹ **Read-only by default** - write operations require explicit write scopes
171
235
 
172
- ## Support
236
+ ---
237
+
238
+ ## ๐Ÿ†˜ Support
173
239
 
174
240
  Questions or issues? Email [support@assetlab.ca](mailto:support@assetlab.ca).
241
+
242
+ Docs: [assetlab.ca](https://assetlab.ca) โ†’ **Docs โ†’ AI & MCP**
@@ -4696,6 +4696,137 @@ export function registerWriteTools(server, client) {
4696
4696
  }
4697
4697
  });
4698
4698
  // ============================================================
4699
+ // Infrastructure Lifecycle Events (scope: infrastructure_lifecycle_events)
4700
+ // ============================================================
4701
+ const lifecycleEventScope = {
4702
+ feature_class: z
4703
+ .string()
4704
+ .regex(/^[a-z][a-z0-9_]{0,49}$/)
4705
+ .optional()
4706
+ .describe('Feature class code the strategy scope applies to (omit for a material-wide scope; at least one of feature_class/material is required)'),
4707
+ material: z
4708
+ .string()
4709
+ .max(200)
4710
+ .optional()
4711
+ .describe('Material the scope applies to, exactly as features carry it (e.g. "PVC")'),
4712
+ diameter_min_mm: z
4713
+ .number()
4714
+ .positive()
4715
+ .optional()
4716
+ .describe('Lower bound of a diameter band (requires material); bands ladder like rates'),
4717
+ };
4718
+ const lifecycleEventFields = {
4719
+ event_class: z
4720
+ .enum(['preventive', 'rehabilitation'])
4721
+ .describe('Event type โ€” preventative maintenance or rehabilitation'),
4722
+ trigger_condition_max: z
4723
+ .number()
4724
+ .int()
4725
+ .min(1)
4726
+ .max(99)
4727
+ .describe('Upper bound of the trigger window โ€” the event fires when projected condition falls to this'),
4728
+ trigger_condition_min: z
4729
+ .number()
4730
+ .int()
4731
+ .min(0)
4732
+ .max(98)
4733
+ .optional()
4734
+ .describe('Lower bound of the trigger window (default 0); a feature already below it has missed the event'),
4735
+ impact_method: z
4736
+ .enum(['add_years', 'reset_condition'])
4737
+ .describe('Effect: add years of life, or reset condition to a value'),
4738
+ impact_add_years: z
4739
+ .number()
4740
+ .positive()
4741
+ .max(100)
4742
+ .optional()
4743
+ .describe('Years added (required when impact_method is add_years)'),
4744
+ impact_reset_to: z
4745
+ .number()
4746
+ .int()
4747
+ .min(1)
4748
+ .max(100)
4749
+ .optional()
4750
+ .describe('Condition after the event (required when impact_method is reset_condition)'),
4751
+ cost_method: z
4752
+ .enum(['per_unit', 'fixed'])
4753
+ .optional()
4754
+ .describe("Costing: per unit (uses the feature's measured quantity and unit) or a fixed amount (default per_unit)"),
4755
+ unit_cost: z
4756
+ .number()
4757
+ .min(0)
4758
+ .optional()
4759
+ .describe('Cost per unit (current dollars, never indexed)'),
4760
+ fixed_cost: z.number().min(0).optional().describe('Fixed cost (current dollars)'),
4761
+ cost_source: z
4762
+ .string()
4763
+ .max(200)
4764
+ .optional()
4765
+ .describe('Provenance of the cost ("Engineering 2026", a tender reference)'),
4766
+ max_applications: z
4767
+ .number()
4768
+ .int()
4769
+ .min(1)
4770
+ .max(10)
4771
+ .optional()
4772
+ .describe("How many times the event may fire over a feature's life (default 1)"),
4773
+ min_years_between: z
4774
+ .number()
4775
+ .min(1)
4776
+ .max(100)
4777
+ .optional()
4778
+ .describe('Minimum years between firings of a recurring event (default 1)'),
4779
+ sort_order: z.number().int().min(0).optional().describe('Evaluation order within the strategy'),
4780
+ };
4781
+ server.tool('create_infrastructure_lifecycle_event', 'Create a lifecycle strategy event. Events attach to a SCOPE (feature_class code, material, optional diameter band) โ€” never to individual features; every feature resolves the most specific matching scope, like replacement rates. Replacement is NOT an event (it is priced by the rates and scheduled by the renewal forecast) โ€” model the interventions BEFORE replacement: crack sealing, relining, resurfacing. List existing events first to reuse a scope. Requires infrastructure_lifecycle_events:write scope.', {
4782
+ name: z.string().max(200).describe('Event name (required, e.g. "Crack Sealing")'),
4783
+ ...lifecycleEventScope,
4784
+ ...lifecycleEventFields,
4785
+ }, async (params) => {
4786
+ try {
4787
+ const result = await client.create('infrastructure-lifecycle-events', buildBody(params));
4788
+ return formatResult(result);
4789
+ }
4790
+ catch (err) {
4791
+ return formatError(err);
4792
+ }
4793
+ });
4794
+ server.tool('update_infrastructure_lifecycle_event', 'Update a lifecycle strategy event by ID. Editing re-confirms the cost (cost_reviewed_on is server-set). Set is_active false to disable an event without deleting it โ€” projections recompute immediately. Requires infrastructure_lifecycle_events:write scope.', {
4795
+ id: z.string().uuid().describe('Lifecycle event ID'),
4796
+ name: z.string().max(200).optional().describe('Event name'),
4797
+ is_active: z.boolean().optional().describe('Disable/enable the event'),
4798
+ event_class: lifecycleEventFields.event_class.optional(),
4799
+ trigger_condition_max: lifecycleEventFields.trigger_condition_max.optional(),
4800
+ trigger_condition_min: lifecycleEventFields.trigger_condition_min,
4801
+ impact_method: lifecycleEventFields.impact_method.optional(),
4802
+ impact_add_years: lifecycleEventFields.impact_add_years,
4803
+ impact_reset_to: lifecycleEventFields.impact_reset_to,
4804
+ cost_method: lifecycleEventFields.cost_method,
4805
+ unit_cost: lifecycleEventFields.unit_cost,
4806
+ fixed_cost: lifecycleEventFields.fixed_cost,
4807
+ cost_source: lifecycleEventFields.cost_source,
4808
+ max_applications: lifecycleEventFields.max_applications,
4809
+ min_years_between: lifecycleEventFields.min_years_between,
4810
+ sort_order: lifecycleEventFields.sort_order,
4811
+ }, async ({ id, ...rest }) => {
4812
+ try {
4813
+ const result = await client.update('infrastructure-lifecycle-events', id, buildBody(rest));
4814
+ return formatResult(result);
4815
+ }
4816
+ catch (err) {
4817
+ return formatError(err);
4818
+ }
4819
+ });
4820
+ server.tool('delete_infrastructure_lifecycle_event', 'Delete a lifecycle strategy event by ID. Projections recompute immediately; consider update with is_active=false to disable instead. Requires infrastructure_lifecycle_events:write scope.', { id: z.string().uuid().describe('Lifecycle event ID') }, async ({ id }) => {
4821
+ try {
4822
+ const result = await client.remove('infrastructure-lifecycle-events', id);
4823
+ return formatResult(result);
4824
+ }
4825
+ catch (err) {
4826
+ return formatError(err);
4827
+ }
4828
+ });
4829
+ // ============================================================
4699
4830
  // Infrastructure Networks (scope: infrastructure_networks)
4700
4831
  // ============================================================
4701
4832
  server.tool('create_infrastructure_network', 'Create an infrastructure network โ€” a named grouping of features bound to one feature class. Requires infrastructure_networks:write scope.', {