@assetlab/mcp-server 1.5.1 → 1.7.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.
@@ -125,11 +125,11 @@ export function registerWriteTools(server, client) {
125
125
  salvage_value_percentage: z.number().min(0).max(100).optional().describe('Salvage value percentage (0-100)'),
126
126
  consequence_of_failure_score: z.number().int().min(0).optional().describe('Consequence of failure score'),
127
127
  likelihood_of_failure_score: z.number().int().min(0).optional().describe('Likelihood of failure score'),
128
- safety_impact: z.string().optional().describe('Safety impact description'),
129
- service_impact: z.string().optional().describe('Service impact description'),
130
- environmental_impact: z.string().optional().describe('Environmental impact description'),
131
- regulatory_impact: z.string().optional().describe('Regulatory impact description'),
132
- reputation_impact: z.string().optional().describe('Reputation impact description'),
128
+ safety_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Safety impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
129
+ service_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Service impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
130
+ environmental_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Environmental impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
131
+ regulatory_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Regulatory impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
132
+ reputation_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Reputation impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
133
133
  }, async (params) => {
134
134
  try {
135
135
  const result = await client.create('assets', buildBody(params));
@@ -170,11 +170,11 @@ export function registerWriteTools(server, client) {
170
170
  salvage_value_percentage: z.number().min(0).max(100).optional().describe('Salvage value percentage (0-100)'),
171
171
  consequence_of_failure_score: z.number().int().min(0).optional().describe('Consequence of failure score'),
172
172
  likelihood_of_failure_score: z.number().int().min(0).optional().describe('Likelihood of failure score'),
173
- safety_impact: z.string().optional().describe('Safety impact description'),
174
- service_impact: z.string().optional().describe('Service impact description'),
175
- environmental_impact: z.string().optional().describe('Environmental impact description'),
176
- regulatory_impact: z.string().optional().describe('Regulatory impact description'),
177
- reputation_impact: z.string().optional().describe('Reputation impact description'),
173
+ safety_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Safety impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
174
+ service_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Service impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
175
+ environmental_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Environmental impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
176
+ regulatory_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Regulatory impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
177
+ reputation_impact: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional().describe('Reputation impact level (LOW, MEDIUM, HIGH, CRITICAL)'),
178
178
  }, async ({ id, ...rest }) => {
179
179
  try {
180
180
  const result = await client.update('assets', id, buildBody(rest));
@@ -259,8 +259,8 @@ export function registerWriteTools(server, client) {
259
259
  state: z.string().max(100).optional().describe('State/province'),
260
260
  country: z.string().max(100).optional().describe('Country'),
261
261
  status: z.string().max(50).optional().describe('Vendor status'),
262
- category: z.string().max(100).optional().describe('Vendor category'),
263
- website: z.string().max(500).optional().describe('Website URL'),
262
+ categories: z.array(z.string().max(100)).optional().describe('Vendor categories (e.g. ["HVAC", "Plumbing"])'),
263
+ website: z.string().max(500).optional().describe('Website URL (protocol and www prefix are stripped automatically)'),
264
264
  description: z.string().optional().describe('Description'),
265
265
  }, async (params) => {
266
266
  try {
@@ -282,8 +282,8 @@ export function registerWriteTools(server, client) {
282
282
  state: z.string().max(100).optional().describe('State/province'),
283
283
  country: z.string().max(100).optional().describe('Country'),
284
284
  status: z.string().max(50).optional().describe('Vendor status'),
285
- category: z.string().max(100).optional().describe('Vendor category'),
286
- website: z.string().max(500).optional().describe('Website URL'),
285
+ categories: z.array(z.string().max(100)).optional().describe('Vendor categories (e.g. ["HVAC", "Plumbing"])'),
286
+ website: z.string().max(500).optional().describe('Website URL (protocol and www prefix are stripped automatically)'),
287
287
  description: z.string().optional().describe('Description'),
288
288
  }, async ({ id, ...rest }) => {
289
289
  try {
@@ -485,6 +485,11 @@ export function registerWriteTools(server, client) {
485
485
  asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
486
486
  system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
487
487
  location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
488
+ tasks: z.array(z.object({
489
+ id: z.string().describe('Unique task ID (use a random string)'),
490
+ description: z.string().describe('Task description'),
491
+ completed: z.boolean().describe('Whether the task is completed'),
492
+ })).optional().describe('Checklist of tasks for this PM schedule'),
488
493
  }, async (params) => {
489
494
  try {
490
495
  const result = await client.create('pm-schedules', buildBody(params));
@@ -519,6 +524,11 @@ export function registerWriteTools(server, client) {
519
524
  asset_ids: z.array(z.string().uuid()).optional().describe('Array of asset IDs'),
520
525
  system_ids: z.array(z.string().uuid()).optional().describe('Array of system IDs'),
521
526
  location_ids: z.array(z.string().uuid()).optional().describe('Array of location IDs'),
527
+ tasks: z.array(z.object({
528
+ id: z.string().describe('Unique task ID'),
529
+ description: z.string().describe('Task description'),
530
+ completed: z.boolean().describe('Whether the task is completed'),
531
+ })).optional().describe('Checklist of tasks for this PM schedule'),
522
532
  }, async ({ id, ...rest }) => {
523
533
  try {
524
534
  const result = await client.update('pm-schedules', id, buildBody(rest));
@@ -545,7 +555,7 @@ export function registerWriteTools(server, client) {
545
555
  status: z.string().max(100).describe('Project status (required)'),
546
556
  start_date: z.string().describe('Start date (ISO 8601, required)'),
547
557
  project_code: z.string().max(100).optional().describe('Project code'),
548
- project_type: z.string().max(100).optional().describe('Project type'),
558
+ project_type: z.enum(['capital', 'maintenance', 'repair', 'upgrade', 'new_construction', 'renovation', 'deferred_maintenance', 'other']).optional().describe('Project type'),
549
559
  current_phase: z.string().max(100).optional().describe('Current phase'),
550
560
  description: z.string().optional().describe('Description'),
551
561
  end_date: z.string().optional().describe('End date (ISO 8601)'),
@@ -569,7 +579,7 @@ export function registerWriteTools(server, client) {
569
579
  status: z.string().max(100).optional().describe('Project status'),
570
580
  start_date: z.string().optional().describe('Start date (ISO 8601)'),
571
581
  project_code: z.string().max(100).optional().describe('Project code'),
572
- project_type: z.string().max(100).optional().describe('Project type'),
582
+ project_type: z.enum(['capital', 'maintenance', 'repair', 'upgrade', 'new_construction', 'renovation', 'deferred_maintenance', 'other']).optional().describe('Project type'),
573
583
  current_phase: z.string().max(100).optional().describe('Current phase'),
574
584
  description: z.string().optional().describe('Description'),
575
585
  end_date: z.string().optional().describe('End date (ISO 8601)'),
@@ -981,7 +991,7 @@ export function registerWriteTools(server, client) {
981
991
  // 18. Asset Costs (scope: asset_costs)
982
992
  // ============================================================
983
993
  server.tool('create_asset_cost', 'Create a new asset cost entry. Requires asset_costs:write scope.', {
984
- category: z.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission']).describe('Cost category (required)'),
994
+ category: z.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other']).describe('Cost category (required)'),
985
995
  amount: z.number().min(0).describe('Cost amount (required)'),
986
996
  cost_date: z.string().describe('Cost date (ISO 8601, required)'),
987
997
  asset_id: z.string().uuid().optional().describe('Asset ID'),
@@ -989,7 +999,6 @@ export function registerWriteTools(server, client) {
989
999
  building_id: z.string().uuid().optional().describe('Building ID'),
990
1000
  work_order_id: z.string().uuid().optional().describe('Work order ID'),
991
1001
  description: z.string().optional().describe('Description'),
992
- vendor_id: z.string().uuid().optional().describe('Vendor ID'),
993
1002
  }, async (params) => {
994
1003
  try {
995
1004
  const result = await client.create('asset-costs', buildBody(params));
@@ -1001,7 +1010,7 @@ export function registerWriteTools(server, client) {
1001
1010
  });
1002
1011
  server.tool('update_asset_cost', 'Update an existing asset cost entry by ID. Requires asset_costs:write scope.', {
1003
1012
  id: z.string().uuid().describe('Asset cost ID'),
1004
- category: z.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission']).optional().describe('Cost category'),
1013
+ category: z.enum(['Repair', 'PM', 'Operation', 'Replacement', 'Decommission', 'Other']).optional().describe('Cost category'),
1005
1014
  amount: z.number().min(0).optional().describe('Cost amount'),
1006
1015
  cost_date: z.string().optional().describe('Cost date (ISO 8601)'),
1007
1016
  asset_id: z.string().uuid().optional().describe('Asset ID'),
@@ -1009,7 +1018,6 @@ export function registerWriteTools(server, client) {
1009
1018
  building_id: z.string().uuid().optional().describe('Building ID'),
1010
1019
  work_order_id: z.string().uuid().optional().describe('Work order ID'),
1011
1020
  description: z.string().optional().describe('Description'),
1012
- vendor_id: z.string().uuid().optional().describe('Vendor ID'),
1013
1021
  }, async ({ id, ...rest }) => {
1014
1022
  try {
1015
1023
  const result = await client.update('asset-costs', id, buildBody(rest));
@@ -1935,6 +1943,388 @@ export function registerWriteTools(server, client) {
1935
1943
  }
1936
1944
  });
1937
1945
  // ============================================================
1946
+ // Upload URLs
1947
+ // ============================================================
1948
+ server.tool('create_upload_url', 'Generate a signed upload URL for uploading a file to AssetLab storage. Returns a signed_url to PUT the file to, plus the path to reference when creating a document record. Requires upload_urls:write scope.', {
1949
+ bucket: z.enum(['documents', 'attachments', 'project-documents', 'contract-documents']).describe('Storage bucket (required)'),
1950
+ file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
1951
+ }, async (params) => {
1952
+ try {
1953
+ const result = await client.create('upload-urls', buildBody(params));
1954
+ return formatResult(result);
1955
+ }
1956
+ catch (err) {
1957
+ return formatError(err);
1958
+ }
1959
+ });
1960
+ // ============================================================
1961
+ // Asset Documents (scope: asset_documents)
1962
+ // ============================================================
1963
+ server.tool('create_asset_document', 'Create an asset document record (after uploading the file via create_upload_url). Requires asset_documents:write scope.', {
1964
+ name: z.string().min(1).max(500).describe('Document name (required)'),
1965
+ file_path: z.string().min(1).max(2000).describe('Storage path from upload URL response (required)'),
1966
+ asset_id: z.string().uuid().describe('Asset ID this document belongs to (required)'),
1967
+ category: z.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other']).optional().describe('Document category'),
1968
+ description: z.string().optional().describe('Description'),
1969
+ file_type: z.string().max(200).optional().describe('MIME type'),
1970
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
1971
+ user_id: z.string().max(200).optional().describe('Uploader user ID'),
1972
+ }, async (params) => {
1973
+ try {
1974
+ const result = await client.create('asset-documents', buildBody(params));
1975
+ return formatResult(result);
1976
+ }
1977
+ catch (err) {
1978
+ return formatError(err);
1979
+ }
1980
+ });
1981
+ server.tool('update_asset_document', 'Update an asset document by ID. Requires asset_documents:write scope.', {
1982
+ id: z.string().uuid().describe('Asset document ID'),
1983
+ name: z.string().min(1).max(500).optional().describe('Document name'),
1984
+ file_path: z.string().max(2000).optional().describe('Storage path'),
1985
+ asset_id: z.string().uuid().optional().describe('Asset ID'),
1986
+ category: z.enum(['om', 'commissioning', 'warranty', 'installation', 'specification', 'other']).optional().describe('Document category'),
1987
+ description: z.string().optional().describe('Description'),
1988
+ file_type: z.string().max(200).optional().describe('MIME type'),
1989
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
1990
+ user_id: z.string().max(200).optional().describe('Uploader user ID'),
1991
+ }, async ({ id, ...rest }) => {
1992
+ try {
1993
+ const result = await client.update('asset-documents', id, buildBody(rest));
1994
+ return formatResult(result);
1995
+ }
1996
+ catch (err) {
1997
+ return formatError(err);
1998
+ }
1999
+ });
2000
+ server.tool('delete_asset_document', 'Delete an asset document by ID. Requires asset_documents:write scope.', { id: z.string().uuid().describe('Asset document ID') }, async ({ id }) => {
2001
+ try {
2002
+ const result = await client.remove('asset-documents', id);
2003
+ return formatResult(result);
2004
+ }
2005
+ catch (err) {
2006
+ return formatError(err);
2007
+ }
2008
+ });
2009
+ // ============================================================
2010
+ // Attachments (scope: attachments)
2011
+ // ============================================================
2012
+ server.tool('create_attachment', 'Create an attachment record linked to a work order, work request, PM schedule, or PM template. Exactly one parent ID must be provided. Requires attachments:write scope.', {
2013
+ file_url: z.string().min(1).max(2000).describe('File URL / storage path (required)'),
2014
+ file_name: z.string().min(1).max(500).describe('File name (required)'),
2015
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2016
+ file_type: z.string().max(200).optional().describe('MIME type'),
2017
+ uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
2018
+ description: z.string().optional().describe('Description'),
2019
+ work_order_id: z.string().uuid().optional().describe('Work order ID (exactly one parent required)'),
2020
+ work_request_id: z.string().uuid().optional().describe('Work request ID (exactly one parent required)'),
2021
+ pm_schedule_id: z.string().uuid().optional().describe('PM schedule ID (exactly one parent required)'),
2022
+ pm_template_id: z.string().uuid().optional().describe('PM template ID (exactly one parent required)'),
2023
+ }, async (params) => {
2024
+ try {
2025
+ const result = await client.create('attachments', buildBody(params));
2026
+ return formatResult(result);
2027
+ }
2028
+ catch (err) {
2029
+ return formatError(err);
2030
+ }
2031
+ });
2032
+ server.tool('update_attachment', 'Update an attachment by ID. Requires attachments:write scope.', {
2033
+ id: z.string().uuid().describe('Attachment ID'),
2034
+ file_url: z.string().max(2000).optional().describe('File URL / storage path'),
2035
+ file_name: z.string().max(500).optional().describe('File name'),
2036
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2037
+ file_type: z.string().max(200).optional().describe('MIME type'),
2038
+ uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
2039
+ description: z.string().optional().describe('Description'),
2040
+ work_order_id: z.string().uuid().optional().describe('Work order ID'),
2041
+ work_request_id: z.string().uuid().optional().describe('Work request ID'),
2042
+ pm_schedule_id: z.string().uuid().optional().describe('PM schedule ID'),
2043
+ pm_template_id: z.string().uuid().optional().describe('PM template ID'),
2044
+ }, async ({ id, ...rest }) => {
2045
+ try {
2046
+ const result = await client.update('attachments', id, buildBody(rest));
2047
+ return formatResult(result);
2048
+ }
2049
+ catch (err) {
2050
+ return formatError(err);
2051
+ }
2052
+ });
2053
+ server.tool('delete_attachment', 'Delete an attachment by ID. Requires attachments:write scope.', { id: z.string().uuid().describe('Attachment ID') }, async ({ id }) => {
2054
+ try {
2055
+ const result = await client.remove('attachments', id);
2056
+ return formatResult(result);
2057
+ }
2058
+ catch (err) {
2059
+ return formatError(err);
2060
+ }
2061
+ });
2062
+ // ============================================================
2063
+ // Project Documents (scope: project_documents)
2064
+ // ============================================================
2065
+ server.tool('create_project_document', 'Create a project document record. Requires project_documents:write scope.', {
2066
+ project_id: z.string().uuid().describe('Project ID (required)'),
2067
+ name: z.string().min(1).max(500).describe('Document name (required)'),
2068
+ file_path: z.string().min(1).max(2000).describe('Storage path from upload URL response (required)'),
2069
+ uploaded_by: z.string().min(1).max(200).describe('Uploader user ID (required)'),
2070
+ folder_id: z.string().uuid().optional().describe('Folder ID'),
2071
+ description: z.string().optional().describe('Description'),
2072
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2073
+ file_type: z.string().max(200).optional().describe('MIME type'),
2074
+ }, async (params) => {
2075
+ try {
2076
+ const result = await client.create('project-documents', buildBody(params));
2077
+ return formatResult(result);
2078
+ }
2079
+ catch (err) {
2080
+ return formatError(err);
2081
+ }
2082
+ });
2083
+ server.tool('update_project_document', 'Update a project document by ID. Requires project_documents:write scope.', {
2084
+ id: z.string().uuid().describe('Project document ID'),
2085
+ project_id: z.string().uuid().optional().describe('Project ID'),
2086
+ name: z.string().min(1).max(500).optional().describe('Document name'),
2087
+ file_path: z.string().max(2000).optional().describe('Storage path'),
2088
+ uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
2089
+ folder_id: z.string().uuid().optional().describe('Folder ID'),
2090
+ description: z.string().optional().describe('Description'),
2091
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2092
+ file_type: z.string().max(200).optional().describe('MIME type'),
2093
+ }, async ({ id, ...rest }) => {
2094
+ try {
2095
+ const result = await client.update('project-documents', id, buildBody(rest));
2096
+ return formatResult(result);
2097
+ }
2098
+ catch (err) {
2099
+ return formatError(err);
2100
+ }
2101
+ });
2102
+ server.tool('delete_project_document', 'Delete a project document by ID. Requires project_documents:write scope.', { id: z.string().uuid().describe('Project document ID') }, async ({ id }) => {
2103
+ try {
2104
+ const result = await client.remove('project-documents', id);
2105
+ return formatResult(result);
2106
+ }
2107
+ catch (err) {
2108
+ return formatError(err);
2109
+ }
2110
+ });
2111
+ // ============================================================
2112
+ // Contract Documents (scope: contract_documents)
2113
+ // ============================================================
2114
+ server.tool('create_contract_document', 'Create a contract document record. Requires contract_documents:write scope.', {
2115
+ contract_id: z.string().uuid().describe('Contract ID (required)'),
2116
+ file_name: z.string().min(1).max(500).describe('File name (required)'),
2117
+ file_path: z.string().min(1).max(2000).describe('Storage path from upload URL response (required)'),
2118
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2119
+ file_type: z.string().max(200).optional().describe('MIME type'),
2120
+ uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
2121
+ }, async (params) => {
2122
+ try {
2123
+ const result = await client.create('contract-documents', buildBody(params));
2124
+ return formatResult(result);
2125
+ }
2126
+ catch (err) {
2127
+ return formatError(err);
2128
+ }
2129
+ });
2130
+ server.tool('update_contract_document', 'Update a contract document by ID. Requires contract_documents:write scope.', {
2131
+ id: z.string().uuid().describe('Contract document ID'),
2132
+ contract_id: z.string().uuid().optional().describe('Contract ID'),
2133
+ file_name: z.string().max(500).optional().describe('File name'),
2134
+ file_path: z.string().max(2000).optional().describe('Storage path'),
2135
+ file_size: z.number().min(0).optional().describe('File size in bytes'),
2136
+ file_type: z.string().max(200).optional().describe('MIME type'),
2137
+ uploaded_by: z.string().max(200).optional().describe('Uploader user ID'),
2138
+ }, async ({ id, ...rest }) => {
2139
+ try {
2140
+ const result = await client.update('contract-documents', id, buildBody(rest));
2141
+ return formatResult(result);
2142
+ }
2143
+ catch (err) {
2144
+ return formatError(err);
2145
+ }
2146
+ });
2147
+ server.tool('delete_contract_document', 'Delete a contract document by ID. Requires contract_documents:write scope.', { id: z.string().uuid().describe('Contract document ID') }, async ({ id }) => {
2148
+ try {
2149
+ const result = await client.remove('contract-documents', id);
2150
+ return formatResult(result);
2151
+ }
2152
+ catch (err) {
2153
+ return formatError(err);
2154
+ }
2155
+ });
2156
+ // ============================================================
2157
+ // Project Team Members
2158
+ // ============================================================
2159
+ server.tool('create_project_team_member', 'Add a team member to a project. Requires project_team_members:write scope.', {
2160
+ project_id: z.string().uuid().describe('Project ID (required)'),
2161
+ user_id: z.string().min(1).max(200).describe('Clerk user ID (required)'),
2162
+ role: z.string().min(1).max(100).describe('Role on the project (required)'),
2163
+ responsibilities: z.string().optional().describe('Description of responsibilities'),
2164
+ start_date: z.string().optional().describe('Start date (ISO 8601)'),
2165
+ end_date: z.string().optional().describe('End date (ISO 8601)'),
2166
+ is_active: z.boolean().optional().describe('Whether member is currently active'),
2167
+ }, async (params) => {
2168
+ try {
2169
+ const result = await client.create('project-team-members', buildBody(params));
2170
+ return formatResult(result);
2171
+ }
2172
+ catch (err) {
2173
+ return formatError(err);
2174
+ }
2175
+ });
2176
+ server.tool('update_project_team_member', 'Update a project team member by ID. Requires project_team_members:write scope.', {
2177
+ id: z.string().uuid().describe('Project team member ID'),
2178
+ project_id: z.string().uuid().optional().describe('Project ID'),
2179
+ user_id: z.string().min(1).max(200).optional().describe('Clerk user ID'),
2180
+ role: z.string().min(1).max(100).optional().describe('Role on the project'),
2181
+ responsibilities: z.string().optional().describe('Description of responsibilities'),
2182
+ start_date: z.string().optional().describe('Start date (ISO 8601)'),
2183
+ end_date: z.string().optional().describe('End date (ISO 8601)'),
2184
+ is_active: z.boolean().optional().describe('Whether member is currently active'),
2185
+ }, async ({ id, ...rest }) => {
2186
+ try {
2187
+ const result = await client.update('project-team-members', id, buildBody(rest));
2188
+ return formatResult(result);
2189
+ }
2190
+ catch (err) {
2191
+ return formatError(err);
2192
+ }
2193
+ });
2194
+ server.tool('delete_project_team_member', 'Remove a team member from a project by ID. Requires project_team_members:write scope.', { id: z.string().uuid().describe('Project team member ID') }, async ({ id }) => {
2195
+ try {
2196
+ const result = await client.remove('project-team-members', id);
2197
+ return formatResult(result);
2198
+ }
2199
+ catch (err) {
2200
+ return formatError(err);
2201
+ }
2202
+ });
2203
+ // ============================================================
2204
+ // Project Task Dependencies
2205
+ // ============================================================
2206
+ server.tool('create_project_task_dependency', 'Create a dependency between two project tasks. Requires project_task_dependencies:write scope.', {
2207
+ task_id: z.string().uuid().describe('Task ID (the dependent task, required)'),
2208
+ depends_on_task_id: z.string().uuid().describe('Task ID that must complete first (required)'),
2209
+ dependency_type: z.enum(['finish_to_start', 'start_to_start', 'finish_to_finish', 'start_to_finish']).optional().describe('Dependency type (default: finish_to_start)'),
2210
+ }, async (params) => {
2211
+ try {
2212
+ const result = await client.create('project-task-dependencies', buildBody(params));
2213
+ return formatResult(result);
2214
+ }
2215
+ catch (err) {
2216
+ return formatError(err);
2217
+ }
2218
+ });
2219
+ server.tool('delete_project_task_dependency', 'Delete a task dependency by ID. Requires project_task_dependencies:write scope.', { id: z.string().uuid().describe('Project task dependency ID') }, async ({ id }) => {
2220
+ try {
2221
+ const result = await client.remove('project-task-dependencies', id);
2222
+ return formatResult(result);
2223
+ }
2224
+ catch (err) {
2225
+ return formatError(err);
2226
+ }
2227
+ });
2228
+ // ============================================================
2229
+ // Project Updates
2230
+ // ============================================================
2231
+ server.tool('create_project_update', 'Create a periodic project status update. Requires project_updates:write scope.', {
2232
+ project_id: z.string().uuid().describe('Project ID (required)'),
2233
+ author_id: z.string().min(1).max(200).describe('Author Clerk user ID (required)'),
2234
+ timeframe: z.enum(['monthly', 'quarterly', 'bi-annually', 'annually']).describe('Update timeframe (required)'),
2235
+ period_year: z.number().int().min(2000).max(2100).describe('Year for this update period (required)'),
2236
+ period_value: z.string().min(1).max(20).describe('Period value — 1-12 for monthly, 1-4 for quarterly, etc. (required)'),
2237
+ content: z.string().min(1).describe('Update content (required)'),
2238
+ title: z.string().max(500).optional().describe('Optional custom title'),
2239
+ }, async (params) => {
2240
+ try {
2241
+ const result = await client.create('project-updates', buildBody(params));
2242
+ return formatResult(result);
2243
+ }
2244
+ catch (err) {
2245
+ return formatError(err);
2246
+ }
2247
+ });
2248
+ server.tool('update_project_update', 'Update an existing project update by ID. Requires project_updates:write scope.', {
2249
+ id: z.string().uuid().describe('Project update ID'),
2250
+ project_id: z.string().uuid().optional().describe('Project ID'),
2251
+ author_id: z.string().min(1).max(200).optional().describe('Author Clerk user ID'),
2252
+ timeframe: z.enum(['monthly', 'quarterly', 'bi-annually', 'annually']).optional().describe('Update timeframe'),
2253
+ period_year: z.number().int().min(2000).max(2100).optional().describe('Year for this update period'),
2254
+ period_value: z.string().min(1).max(20).optional().describe('Period value'),
2255
+ content: z.string().min(1).optional().describe('Update content'),
2256
+ title: z.string().max(500).optional().describe('Optional custom title'),
2257
+ }, async ({ id, ...rest }) => {
2258
+ try {
2259
+ const result = await client.update('project-updates', id, buildBody(rest));
2260
+ return formatResult(result);
2261
+ }
2262
+ catch (err) {
2263
+ return formatError(err);
2264
+ }
2265
+ });
2266
+ server.tool('delete_project_update', 'Delete a project update by ID. Requires project_updates:write scope.', { id: z.string().uuid().describe('Project update ID') }, async ({ id }) => {
2267
+ try {
2268
+ const result = await client.remove('project-updates', id);
2269
+ return formatResult(result);
2270
+ }
2271
+ catch (err) {
2272
+ return formatError(err);
2273
+ }
2274
+ });
2275
+ // ============================================================
2276
+ // Project Cost Snapshots
2277
+ // ============================================================
2278
+ server.tool('create_project_cost_snapshot', 'Record a cost snapshot for a project at a point in time. Requires project_cost_snapshots:write scope.', {
2279
+ project_id: z.string().uuid().describe('Project ID (required)'),
2280
+ snapshot_date: z.string().describe('Snapshot date (ISO 8601, required)'),
2281
+ total_budget: z.number().min(0).describe('Total budget amount (required)'),
2282
+ actual_cost: z.number().min(0).describe('Actual cost to date (required)'),
2283
+ forecasted_cost: z.number().min(0).optional().describe('Forecasted total cost'),
2284
+ percent_complete: z.number().min(0).max(100).optional().describe('Completion percentage (0-100)'),
2285
+ }, async (params) => {
2286
+ try {
2287
+ const result = await client.create('project-cost-snapshots', buildBody(params));
2288
+ return formatResult(result);
2289
+ }
2290
+ catch (err) {
2291
+ return formatError(err);
2292
+ }
2293
+ });
2294
+ server.tool('delete_project_cost_snapshot', 'Delete a project cost snapshot by ID. Requires project_cost_snapshots:write scope.', { id: z.string().uuid().describe('Project cost snapshot ID') }, async ({ id }) => {
2295
+ try {
2296
+ const result = await client.remove('project-cost-snapshots', id);
2297
+ return formatResult(result);
2298
+ }
2299
+ catch (err) {
2300
+ return formatError(err);
2301
+ }
2302
+ });
2303
+ // ============================================================
2304
+ // Project Locations
2305
+ // ============================================================
2306
+ server.tool('create_project_location', 'Link a location to a project. Requires project_locations:write scope.', {
2307
+ project_id: z.string().uuid().describe('Project ID (required)'),
2308
+ location_id: z.string().uuid().describe('Location ID (required)'),
2309
+ }, async (params) => {
2310
+ try {
2311
+ const result = await client.create('project-locations', buildBody(params));
2312
+ return formatResult(result);
2313
+ }
2314
+ catch (err) {
2315
+ return formatError(err);
2316
+ }
2317
+ });
2318
+ server.tool('delete_project_location', 'Remove a location from a project by ID. Requires project_locations:write scope.', { id: z.string().uuid().describe('Project location ID') }, async ({ id }) => {
2319
+ try {
2320
+ const result = await client.remove('project-locations', id);
2321
+ return formatResult(result);
2322
+ }
2323
+ catch (err) {
2324
+ return formatError(err);
2325
+ }
2326
+ });
2327
+ // ============================================================
1938
2328
  // Bulk operations
1939
2329
  // ============================================================
1940
2330
  const BULK_RESOURCES = [
@@ -1946,9 +2336,12 @@ export function registerWriteTools(server, client) {
1946
2336
  'asset-comments', 'asset-costs', 'asset-replacement-plans',
1947
2337
  'work-order-comments', 'project-tasks', 'project-milestones',
1948
2338
  'project-phases', 'project-budget-items', 'project-time-entries',
1949
- 'project-comments', 'parts', 'part-categories',
2339
+ 'project-comments', 'project-team-members', 'project-task-dependencies',
2340
+ 'project-updates', 'project-cost-snapshots', 'project-locations',
2341
+ 'parts', 'part-categories',
1950
2342
  'custom-field-definitions', 'custom-field-values',
1951
2343
  'vendor-site-assignments', 'contract-sites',
2344
+ 'asset-documents', 'attachments', 'project-documents', 'contract-documents',
1952
2345
  ];
1953
2346
  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.', {
1954
2347
  resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
@@ -1962,6 +2355,135 @@ export function registerWriteTools(server, client) {
1962
2355
  return formatError(err);
1963
2356
  }
1964
2357
  });
2358
+ // ============================================================
2359
+ // Asset Statuses (scope: asset_statuses)
2360
+ // ============================================================
2361
+ server.tool('create_asset_status', 'Create a new asset status (lifecycle state for assets). Requires asset_statuses:write scope.', {
2362
+ name: z.string().max(500).describe('Status name (required)'),
2363
+ description: z.string().optional().describe('Description'),
2364
+ }, async (params) => {
2365
+ try {
2366
+ const result = await client.create('asset-statuses', buildBody(params));
2367
+ return formatResult(result);
2368
+ }
2369
+ catch (err) {
2370
+ return formatError(err);
2371
+ }
2372
+ });
2373
+ server.tool('update_asset_status', 'Update an existing asset status by ID. Requires asset_statuses:write scope.', {
2374
+ id: z.string().uuid().describe('Asset status ID'),
2375
+ name: z.string().max(500).optional().describe('Status name'),
2376
+ description: z.string().optional().describe('Description'),
2377
+ }, async ({ id, ...rest }) => {
2378
+ try {
2379
+ const result = await client.update('asset-statuses', id, buildBody(rest));
2380
+ return formatResult(result);
2381
+ }
2382
+ catch (err) {
2383
+ return formatError(err);
2384
+ }
2385
+ });
2386
+ server.tool('delete_asset_status', 'Delete an asset status by ID. Requires asset_statuses:write scope.', { id: z.string().uuid().describe('Asset status ID') }, async ({ id }) => {
2387
+ try {
2388
+ const result = await client.remove('asset-statuses', id);
2389
+ return formatResult(result);
2390
+ }
2391
+ catch (err) {
2392
+ return formatError(err);
2393
+ }
2394
+ });
2395
+ // ============================================================
2396
+ // Compliance Items (scope: compliance)
2397
+ // ============================================================
2398
+ server.tool('create_compliance_item', 'Create a new compliance item (regulatory requirement). Requires compliance:write scope.', {
2399
+ name: z.string().max(500).describe('Compliance item name (required)'),
2400
+ description: z.string().optional().describe('Description'),
2401
+ regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
2402
+ compliance_period_months: z.number().int().min(1).optional().describe('Compliance period in months'),
2403
+ status: z.enum(['active', 'archived']).optional().describe('Status'),
2404
+ system_id: z.string().uuid().optional().describe('Associated system ID'),
2405
+ }, async (params) => {
2406
+ try {
2407
+ const result = await client.create('compliance', buildBody(params));
2408
+ return formatResult(result);
2409
+ }
2410
+ catch (err) {
2411
+ return formatError(err);
2412
+ }
2413
+ });
2414
+ server.tool('update_compliance_item', 'Update an existing compliance item by ID. Requires compliance:write scope.', {
2415
+ id: z.string().uuid().describe('Compliance item ID'),
2416
+ name: z.string().max(500).optional().describe('Compliance item name'),
2417
+ description: z.string().optional().describe('Description'),
2418
+ regulation_reference: z.string().max(500).optional().describe('Regulation or code reference'),
2419
+ compliance_period_months: z.number().int().min(1).optional().describe('Compliance period in months'),
2420
+ status: z.enum(['active', 'archived']).optional().describe('Status'),
2421
+ system_id: z.string().uuid().optional().describe('Associated system ID'),
2422
+ }, async ({ id, ...rest }) => {
2423
+ try {
2424
+ const result = await client.update('compliance', id, buildBody(rest));
2425
+ return formatResult(result);
2426
+ }
2427
+ catch (err) {
2428
+ return formatError(err);
2429
+ }
2430
+ });
2431
+ server.tool('delete_compliance_item', 'Delete a compliance item by ID. Requires compliance:write scope.', { id: z.string().uuid().describe('Compliance item ID') }, async ({ id }) => {
2432
+ try {
2433
+ const result = await client.remove('compliance', id);
2434
+ return formatResult(result);
2435
+ }
2436
+ catch (err) {
2437
+ return formatError(err);
2438
+ }
2439
+ });
2440
+ // ============================================================
2441
+ // Compliance Records (scope: compliance_records)
2442
+ // ============================================================
2443
+ server.tool('create_compliance_record', 'Create a new compliance record (audit trail entry). Requires compliance_records:write scope.', {
2444
+ compliance_item_id: z.string().uuid().describe('Compliance item ID (required)'),
2445
+ pm_schedule_id: z.string().uuid().describe('PM schedule ID (required)'),
2446
+ work_order_id: z.string().uuid().describe('Work order ID (required)'),
2447
+ completed_at: z.string().describe('Completion date-time (ISO 8601, required)'),
2448
+ completed_by: z.string().max(200).optional().describe('User ID who completed'),
2449
+ required_frequency_days: z.number().int().min(1).describe('Required frequency in days (required)'),
2450
+ days_since_last_completion: z.number().int().min(0).optional().describe('Days since last completion'),
2451
+ }, async (params) => {
2452
+ try {
2453
+ const result = await client.create('compliance-records', buildBody(params));
2454
+ return formatResult(result);
2455
+ }
2456
+ catch (err) {
2457
+ return formatError(err);
2458
+ }
2459
+ });
2460
+ server.tool('update_compliance_record', 'Update an existing compliance record by ID. Requires compliance_records:write scope.', {
2461
+ id: z.string().uuid().describe('Compliance record ID'),
2462
+ completed_at: z.string().optional().describe('Completion date-time (ISO 8601)'),
2463
+ completed_by: z.string().max(200).optional().describe('User ID who completed'),
2464
+ required_frequency_days: z.number().int().min(1).optional().describe('Required frequency in days'),
2465
+ days_since_last_completion: z.number().int().min(0).optional().describe('Days since last completion'),
2466
+ }, async ({ id, ...rest }) => {
2467
+ try {
2468
+ const result = await client.update('compliance-records', id, buildBody(rest));
2469
+ return formatResult(result);
2470
+ }
2471
+ catch (err) {
2472
+ return formatError(err);
2473
+ }
2474
+ });
2475
+ server.tool('delete_compliance_record', 'Delete a compliance record by ID. Requires compliance_records:write scope.', { id: z.string().uuid().describe('Compliance record ID') }, async ({ id }) => {
2476
+ try {
2477
+ const result = await client.remove('compliance-records', id);
2478
+ return formatResult(result);
2479
+ }
2480
+ catch (err) {
2481
+ return formatError(err);
2482
+ }
2483
+ });
2484
+ // ============================================================
2485
+ // Bulk Operations
2486
+ // ============================================================
1965
2487
  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.', {
1966
2488
  resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),
1967
2489
  items: z.array(z.record(z.unknown())).min(1).max(100).describe('Array of objects to update (max 100). Each must include an "id" field (UUID) plus fields to change.'),