@assetlab/mcp-server 1.27.0 → 2.0.1

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/dist/index.js CHANGED
@@ -13,12 +13,13 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
14
  import { AssetLabClient, loadConfig } from './client.js';
15
15
  import { registerTools, SERVER_INSTRUCTIONS } from './tools.js';
16
+ import { VERSION } from './version.js';
16
17
  async function main() {
17
18
  const config = loadConfig();
18
19
  const client = new AssetLabClient(config);
19
20
  const server = new McpServer({
20
21
  name: 'assetlab',
21
- version: '1.0.0',
22
+ version: VERSION,
22
23
  }, {
23
24
  instructions: SERVER_INSTRUCTIONS,
24
25
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAE/D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;IAEzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE,mBAAmB;KAClC,CACF,CAAA;IAED,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAC3B,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;IAEzC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,EACD;QACE,YAAY,EAAE,mBAAmB;KAClC,CACF,CAAA;IAED,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -20,7 +20,17 @@ function buildBody(params) {
20
20
  return body;
21
21
  }
22
22
  const PM_RESOURCE_TYPES = ['TOOL', 'PART', 'MATERIAL', 'EQUIPMENT'];
23
- const FORM_TEMPLATE_CATEGORIES = ['task_checklist', 'inspection', 'compliance', 'survey'];
23
+ // Shared by the four PM tools: linking a form here makes every work order the schedule
24
+ // generates carry a copy of that form for the technician to fill in.
25
+ const FORM_TEMPLATE_LINK_SCHEMA = z
26
+ .string()
27
+ .uuid()
28
+ .optional()
29
+ .describe('Form template ID to attach to every work order this generates — resolve via list_form_templates. Use a PUBLISHED template: generation resolves the current published version of the form, so a draft attaches nothing until it is published.');
30
+ // Work orders and PM schedules store each association twice — a singular id and a plural array.
31
+ // The API mirrors whichever side is sent, but callers still need the arrays to associate several
32
+ // records at once (and systems, which have no singular field at all).
33
+ const ASSOCIATION_ARRAY_SCHEMA = (description) => z.array(z.string().uuid()).optional().describe(description);
24
34
  const FORM_TEMPLATE_STATUSES = ['draft', 'published', 'archived'];
25
35
  const FORM_ITEM_TYPES = [
26
36
  'section',
@@ -109,8 +119,15 @@ export function registerWriteTools(server, client) {
109
119
  .string()
110
120
  .uuid()
111
121
  .optional()
112
- .describe('Location ID — resolve last via list_locations filtered by building_id'),
113
- asset_id: z.string().uuid().optional().describe('Asset ID'),
122
+ .describe('Location this work order is for — resolve last via list_locations filtered by building_id. The server mirrors it into location_ids, so send this OR location_ids, not a conflicting pair.'),
123
+ asset_id: z
124
+ .string()
125
+ .uuid()
126
+ .optional()
127
+ .describe('Asset this work order is for — resolve via list_assets. The server mirrors it into asset_ids.'),
128
+ location_ids: ASSOCIATION_ARRAY_SCHEMA('Locations this work order covers — use instead of location_id when there is more than one. Sending location_id alone replaces this with that single id.'),
129
+ asset_ids: ASSOCIATION_ARRAY_SCHEMA('Assets this work order covers — use instead of asset_id when there is more than one.'),
130
+ system_ids: ASSOCIATION_ARRAY_SCHEMA('Systems this work order covers — resolve via list_systems. Systems have no singular field; this array is the only way to associate them.'),
114
131
  start_date: z.string().optional().describe('Start date (ISO 8601)'),
115
132
  due_date: z.string().optional().describe('Due date (ISO 8601)'),
116
133
  estimated_time: z.number().min(0).optional().describe('Estimated time in hours'),
@@ -161,8 +178,15 @@ export function registerWriteTools(server, client) {
161
178
  .string()
162
179
  .uuid()
163
180
  .optional()
164
- .describe('Location ID — resolve last via list_locations filtered by building_id'),
165
- asset_id: z.string().uuid().optional().describe('Asset ID'),
181
+ .describe('Location this work order is for — resolve last via list_locations filtered by building_id. The server mirrors it into location_ids, so send this OR location_ids, not a conflicting pair.'),
182
+ asset_id: z
183
+ .string()
184
+ .uuid()
185
+ .optional()
186
+ .describe('Asset this work order is for — resolve via list_assets. The server mirrors it into asset_ids.'),
187
+ location_ids: ASSOCIATION_ARRAY_SCHEMA('Locations this work order covers — use instead of location_id when there is more than one. Sending location_id alone replaces this with that single id.'),
188
+ asset_ids: ASSOCIATION_ARRAY_SCHEMA('Assets this work order covers — use instead of asset_id when there is more than one.'),
189
+ system_ids: ASSOCIATION_ARRAY_SCHEMA('Systems this work order covers — resolve via list_systems. Systems have no singular field; this array is the only way to associate them.'),
166
190
  start_date: z.string().optional().describe('Start date (ISO 8601)'),
167
191
  due_date: z.string().optional().describe('Due date (ISO 8601)'),
168
192
  estimated_time: z.number().min(0).optional().describe('Estimated time in hours'),
@@ -897,9 +921,9 @@ export function registerWriteTools(server, client) {
897
921
  .describe('Meter interval — trigger every N units'),
898
922
  meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
899
923
  start_date: z.string().optional().describe('Start date (ISO 8601)'),
900
- asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
901
- system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
902
- location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
924
+ asset_ids: ASSOCIATION_ARRAY_SCHEMA('Assets this schedule covers — use instead of asset_id when there is more than one.'),
925
+ system_ids: ASSOCIATION_ARRAY_SCHEMA('Systems this schedule covers. Systems have no singular field; this array is the only way to associate them.'),
926
+ location_ids: ASSOCIATION_ARRAY_SCHEMA('Locations this schedule covers — use instead of location_id when there is more than one. Sending location_id alone replaces this with that single id.'),
903
927
  tasks: z
904
928
  .array(z.object({
905
929
  id: z.string().describe('Unique task ID (use a random string)'),
@@ -908,6 +932,7 @@ export function registerWriteTools(server, client) {
908
932
  }))
909
933
  .optional()
910
934
  .describe('Checklist of tasks for this PM schedule'),
935
+ form_template_id: FORM_TEMPLATE_LINK_SCHEMA,
911
936
  }, async (params) => {
912
937
  try {
913
938
  const result = await client.create('pm-schedules', buildBody(params));
@@ -971,9 +996,9 @@ export function registerWriteTools(server, client) {
971
996
  .describe('Meter interval — trigger every N units'),
972
997
  meter_unit: z.string().max(50).optional().describe('Meter unit (km, miles, hours, cycles)'),
973
998
  start_date: z.string().optional().describe('Start date (ISO 8601)'),
974
- asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
975
- system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
976
- location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
999
+ asset_ids: ASSOCIATION_ARRAY_SCHEMA('Assets this schedule covers — use instead of asset_id when there is more than one.'),
1000
+ system_ids: ASSOCIATION_ARRAY_SCHEMA('Systems this schedule covers. Systems have no singular field; this array is the only way to associate them.'),
1001
+ location_ids: ASSOCIATION_ARRAY_SCHEMA('Locations this schedule covers — use instead of location_id when there is more than one. Sending location_id alone replaces this with that single id.'),
977
1002
  tasks: z
978
1003
  .array(z.object({
979
1004
  id: z.string().describe('Unique task ID'),
@@ -982,6 +1007,7 @@ export function registerWriteTools(server, client) {
982
1007
  }))
983
1008
  .optional()
984
1009
  .describe('Checklist of tasks for this PM schedule'),
1010
+ form_template_id: FORM_TEMPLATE_LINK_SCHEMA,
985
1011
  }, async ({ id, ...rest }) => {
986
1012
  try {
987
1013
  const result = await client.update('pm-schedules', id, buildBody(rest));
@@ -1066,6 +1092,7 @@ export function registerWriteTools(server, client) {
1066
1092
  .array(z.string().uuid())
1067
1093
  .optional()
1068
1094
  .describe('Default location IDs to seed on derived schedules'),
1095
+ form_template_id: FORM_TEMPLATE_LINK_SCHEMA,
1069
1096
  }, async (params) => {
1070
1097
  try {
1071
1098
  const result = await client.create('pm-templates', buildBody(normalizePmTemplateBody(params)));
@@ -1129,6 +1156,7 @@ export function registerWriteTools(server, client) {
1129
1156
  documents: z.array(z.record(z.unknown())).optional().describe('Document references'),
1130
1157
  asset_ids: z.array(z.string().uuid()).optional().describe('Default asset IDs'),
1131
1158
  location_ids: z.array(z.string().uuid()).optional().describe('Default location IDs'),
1159
+ form_template_id: FORM_TEMPLATE_LINK_SCHEMA,
1132
1160
  }, async ({ id, ...rest }) => {
1133
1161
  try {
1134
1162
  const result = await client.update('pm-templates', id, buildBody(normalizePmTemplateBody(rest)));
@@ -1153,10 +1181,11 @@ export function registerWriteTools(server, client) {
1153
1181
  server.tool('create_form_template', 'Create a form template — a reusable inspection, checklist, compliance, or survey definition. Step 1 of building a form: create it as a draft, then add questions with create_form_template_item (or bulk_create on form-template-items), then publish with update_form_template status=published (publishing is rejected if any question is malformed). Requires form_templates:write scope.', {
1154
1182
  name: z.string().min(1).max(500).describe('Form template name (required)'),
1155
1183
  description: z.string().max(5000).optional().describe('Description'),
1156
- category: z
1157
- .enum(FORM_TEMPLATE_CATEGORIES)
1184
+ work_category_id: z
1185
+ .string()
1186
+ .uuid()
1158
1187
  .optional()
1159
- .describe('Form category: inspection (default), task_checklist, compliance, or survey. Manufacturer-recommended equipment checks inspection.'),
1188
+ .describe('Work category ID — the same tenant-configured categories used by work orders (e.g. Electrical, Plumbing, HVAC). Look them up with list_work_categories and pick the closest match; omit if none fits.'),
1160
1189
  status: z
1161
1190
  .enum(FORM_TEMPLATE_STATUSES)
1162
1191
  .optional()
@@ -1174,10 +1203,11 @@ export function registerWriteTools(server, client) {
1174
1203
  id: z.string().uuid().describe('Form template ID'),
1175
1204
  name: z.string().min(1).max(500).optional().describe('Form template name'),
1176
1205
  description: z.string().max(5000).optional().describe('Description'),
1177
- category: z
1178
- .enum(FORM_TEMPLATE_CATEGORIES)
1206
+ work_category_id: z
1207
+ .string()
1208
+ .uuid()
1179
1209
  .optional()
1180
- .describe('Form category (task_checklist, inspection, compliance, or survey)'),
1210
+ .describe('Work category ID (look up with list_work_categories)'),
1181
1211
  status: z
1182
1212
  .enum(FORM_TEMPLATE_STATUSES)
1183
1213
  .optional()
@@ -1286,6 +1316,44 @@ export function registerWriteTools(server, client) {
1286
1316
  }
1287
1317
  });
1288
1318
  // ============================================================
1319
+ // 8d. Form responses — attach / detach (scope: form_responses)
1320
+ // ============================================================
1321
+ server.tool('create_form_response', "Attach a published form to one record so it can be filled in — the direct way to put an inspection or checklist on an existing work order. The form's questions are snapshotted at attach time, so later edits to the template never change a form already in progress. To put a form on every work order a PM schedule generates, set form_template_id on the schedule instead of calling this per work order. Answering and completing the form happen in the AssetLab app, not through this API. Requires form_responses:write scope.", {
1322
+ template_id: z
1323
+ .string()
1324
+ .uuid()
1325
+ .describe('Published form template ID — resolve via list_form_templates. A draft or archived template is rejected; publish it first with update_form_template status="published".'),
1326
+ subject_type: z
1327
+ .enum(['work_order', 'pm_schedule', 'infrastructure_asset', 'compliance_record', 'site'])
1328
+ .describe('What kind of record the form is being attached to'),
1329
+ subject_id: z
1330
+ .string()
1331
+ .uuid()
1332
+ .describe('ID of the record — resolve via the matching list tool (list_work_orders, list_pm_schedules, list_infrastructure_assets, list_compliance_records, list_sites)'),
1333
+ }, async (params) => {
1334
+ try {
1335
+ const result = await client.create('form-responses', buildBody(params));
1336
+ return formatResult(result);
1337
+ }
1338
+ catch (err) {
1339
+ return formatError(err);
1340
+ }
1341
+ });
1342
+ server.tool('delete_form_response', 'Remove a form from the record it is attached to, along with any answers already given. Use this before attaching a different form, since a record holds at most one form. Requires form_responses:write scope.', {
1343
+ id: z
1344
+ .string()
1345
+ .uuid()
1346
+ .describe('Form response ID — resolve via list_form_responses filtered by subject_id'),
1347
+ }, async ({ id }) => {
1348
+ try {
1349
+ const result = await client.remove('form-responses', id);
1350
+ return formatResult(result);
1351
+ }
1352
+ catch (err) {
1353
+ return formatError(err);
1354
+ }
1355
+ });
1356
+ // ============================================================
1289
1357
  // 9. Projects (scope: projects)
1290
1358
  // ============================================================
1291
1359
  server.tool('create_project', 'Create a new project. Requires projects:write scope.', {
@@ -2247,9 +2315,10 @@ export function registerWriteTools(server, client) {
2247
2315
  user_id: z.string().min(1).describe('User ID (required)'),
2248
2316
  start_time: z.string().describe('Start time (ISO 8601 datetime, required)'),
2249
2317
  end_time: z.string().optional().describe('End time (ISO 8601 datetime)'),
2250
- hours: z.number().min(0).optional().describe('Hours worked'),
2318
+ duration_minutes: z.number().min(0).optional().describe('Duration worked, in minutes'),
2251
2319
  description: z.string().optional().describe('Description'),
2252
- hourly_rate: z.number().min(0).optional().describe('Hourly rate'),
2320
+ user_name: z.string().max(200).optional().describe('Display name of the user'),
2321
+ is_billable: z.boolean().optional().describe('Whether the time is billable'),
2253
2322
  }, async (params) => {
2254
2323
  try {
2255
2324
  const result = await client.create('project-time-entries', buildBody(params));
@@ -2266,9 +2335,10 @@ export function registerWriteTools(server, client) {
2266
2335
  user_id: z.string().optional().describe('User ID'),
2267
2336
  start_time: z.string().optional().describe('Start time (ISO 8601 datetime)'),
2268
2337
  end_time: z.string().optional().describe('End time (ISO 8601 datetime)'),
2269
- hours: z.number().min(0).optional().describe('Hours worked'),
2338
+ duration_minutes: z.number().min(0).optional().describe('Duration worked, in minutes'),
2270
2339
  description: z.string().optional().describe('Description'),
2271
- hourly_rate: z.number().min(0).optional().describe('Hourly rate'),
2340
+ user_name: z.string().max(200).optional().describe('Display name of the user'),
2341
+ is_billable: z.boolean().optional().describe('Whether the time is billable'),
2272
2342
  }, async ({ id, ...rest }) => {
2273
2343
  try {
2274
2344
  const result = await client.update('project-time-entries', id, buildBody(rest));
@@ -4280,6 +4350,7 @@ export function registerWriteTools(server, client) {
4280
4350
  'pm-templates',
4281
4351
  'form-templates',
4282
4352
  'form-template-items',
4353
+ 'form-responses',
4283
4354
  'projects',
4284
4355
  'contracts',
4285
4356
  'invoices',
@@ -4508,44 +4579,45 @@ export function registerWriteTools(server, client) {
4508
4579
  }
4509
4580
  });
4510
4581
  // ============================================================
4511
- // Infrastructure Asset Classes (scope: infrastructure_asset_classes)
4582
+ // Infrastructure Feature Classes (scope: infrastructure_feature_classes)
4512
4583
  // ============================================================
4513
4584
  const INFRA_ASSET_CLASS_CATEGORIES = [
4514
- 'pavement',
4515
- 'sewer',
4585
+ 'transportation',
4516
4586
  'water',
4517
- 'gas',
4587
+ 'wastewater',
4588
+ 'stormwater',
4589
+ 'structures',
4518
4590
  'electrical',
4519
4591
  'telecom',
4520
- 'structure',
4521
- 'furniture',
4592
+ 'gas',
4593
+ 'roadside',
4522
4594
  'other',
4523
4595
  ];
4524
- const assetClassCode = z
4596
+ const featureClassCode = z
4525
4597
  .string()
4526
4598
  .regex(/^[a-z][a-z0-9_]{0,49}$/)
4527
4599
  .describe('Asset class code (lowercase snake_case, 1-50 chars)');
4528
- 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.', {
4529
- code: assetClassCode,
4600
+ server.tool('create_infrastructure_feature_class', 'Create a tenant-defined infrastructure feature class. Builtin classes are managed by AssetLab and cannot be created via API (is_builtin is always forced to false). Requires infrastructure_feature_classes:write scope.', {
4601
+ code: featureClassCode,
4530
4602
  name: z.string().max(200).describe('Display name (required)'),
4531
4603
  category: z
4532
4604
  .enum(INFRA_ASSET_CLASS_CATEGORIES)
4533
- .describe('Category (pavement, sewer, water, gas, electrical, telecom, structure, furniture, other)'),
4605
+ .describe('Category — municipal service family (transportation, water, wastewater, stormwater, structures, electrical, telecom, gas, roadside, other)'),
4534
4606
  description: z.string().optional().describe('Description'),
4535
4607
  icon: z.string().max(50).optional().describe('Icon name'),
4536
4608
  color: z.string().max(20).optional().describe('Display color (CSS color or hex)'),
4537
4609
  sort_order: z.number().int().optional().describe('Sort order'),
4538
4610
  }, async (params) => {
4539
4611
  try {
4540
- const result = await client.create('infrastructure-asset-classes', buildBody(params));
4612
+ const result = await client.create('infrastructure-feature-classes', buildBody(params));
4541
4613
  return formatResult(result);
4542
4614
  }
4543
4615
  catch (err) {
4544
4616
  return formatError(err);
4545
4617
  }
4546
4618
  });
4547
- 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.', {
4548
- code: assetClassCode,
4619
+ server.tool('update_infrastructure_feature_class', 'Update a tenant-defined infrastructure feature 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_feature_classes:write scope.', {
4620
+ code: featureClassCode,
4549
4621
  name: z.string().max(200).optional().describe('Display name'),
4550
4622
  description: z.string().optional().describe('Description'),
4551
4623
  icon: z.string().max(50).optional().describe('Icon name'),
@@ -4553,16 +4625,16 @@ export function registerWriteTools(server, client) {
4553
4625
  sort_order: z.number().int().optional().describe('Sort order'),
4554
4626
  }, async ({ code, ...rest }) => {
4555
4627
  try {
4556
- const result = await client.update('infrastructure-asset-classes', code, buildBody(rest));
4628
+ const result = await client.update('infrastructure-feature-classes', code, buildBody(rest));
4557
4629
  return formatResult(result);
4558
4630
  }
4559
4631
  catch (err) {
4560
4632
  return formatError(err);
4561
4633
  }
4562
4634
  });
4563
- 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 }) => {
4635
+ server.tool('delete_infrastructure_feature_class', 'Delete a tenant-defined infrastructure feature 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_feature_classes:write scope.', { code: featureClassCode }, async ({ code }) => {
4564
4636
  try {
4565
- const result = await client.remove('infrastructure-asset-classes', code);
4637
+ const result = await client.remove('infrastructure-feature-classes', code);
4566
4638
  return formatResult(result);
4567
4639
  }
4568
4640
  catch (err) {
@@ -4572,9 +4644,9 @@ export function registerWriteTools(server, client) {
4572
4644
  // ============================================================
4573
4645
  // Infrastructure Networks (scope: infrastructure_networks)
4574
4646
  // ============================================================
4575
- server.tool('create_infrastructure_network', 'Create an infrastructure network — a named grouping of features bound to one asset class. Requires infrastructure_networks:write scope.', {
4647
+ server.tool('create_infrastructure_network', 'Create an infrastructure network — a named grouping of features bound to one feature class. Requires infrastructure_networks:write scope.', {
4576
4648
  name: z.string().max(200).describe('Network name (required)'),
4577
- asset_class: assetClassCode.describe('Asset class code (must exist; resolve via list_infrastructure_asset_classes)'),
4649
+ feature_class: featureClassCode.describe('Asset class code (must exist; resolve via list_infrastructure_feature_classes)'),
4578
4650
  description: z.string().optional().describe('Description'),
4579
4651
  color_scheme: z.string().max(50).optional().describe('Display color scheme'),
4580
4652
  metadata: z.record(z.unknown()).optional().describe('Free-form JSON metadata'),
@@ -4590,7 +4662,7 @@ export function registerWriteTools(server, client) {
4590
4662
  server.tool('update_infrastructure_network', 'Update an existing infrastructure network by ID. Requires infrastructure_networks:write scope.', {
4591
4663
  id: z.string().uuid().describe('Infrastructure network ID'),
4592
4664
  name: z.string().max(200).optional().describe('Network name'),
4593
- asset_class: assetClassCode.optional().describe('Asset class code'),
4665
+ feature_class: featureClassCode.optional().describe('Asset class code'),
4594
4666
  description: z.string().optional().describe('Description'),
4595
4667
  color_scheme: z.string().max(50).optional().describe('Display color scheme'),
4596
4668
  metadata: z.record(z.unknown()).optional().describe('Free-form JSON metadata'),