@assetlab/mcp-server 1.12.0 → 1.14.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.
@@ -2664,6 +2664,221 @@ export function registerWriteTools(server, client) {
2664
2664
  }
2665
2665
  });
2666
2666
  // ============================================================
2667
+ // Level of Service: Service Areas (scope: service_areas)
2668
+ // ============================================================
2669
+ server.tool('create_service_area', 'Create a new service area for Level of Service tracking. Requires service_areas:write scope. After creating, link system classes via create_service_area_system_class and sites via create_service_area_site.', {
2670
+ name: z.string().min(1).max(500).describe('Service area name (required, unique per tenant)'),
2671
+ description: z.string().max(2000).optional().describe('Description'),
2672
+ icon: z.string().max(200).optional().describe('Icon name (e.g., "droplets" for water)'),
2673
+ color: z.string().max(50).optional().describe('Hex color code (e.g., "#3B82F6")'),
2674
+ sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
2675
+ is_active: z.boolean().optional().describe('Whether the service area is active (default: true)'),
2676
+ }, async (params) => {
2677
+ try {
2678
+ const result = await client.create('service-areas', buildBody(params));
2679
+ return formatResult(result);
2680
+ }
2681
+ catch (err) {
2682
+ return formatError(err);
2683
+ }
2684
+ });
2685
+ server.tool('update_service_area', 'Update an existing service area by ID. Requires service_areas:write scope.', {
2686
+ id: z.string().uuid().describe('Service area ID'),
2687
+ name: z.string().min(1).max(500).optional().describe('Service area name'),
2688
+ description: z.string().max(2000).optional().describe('Description'),
2689
+ icon: z.string().max(200).optional().describe('Icon name'),
2690
+ color: z.string().max(50).optional().describe('Hex color code'),
2691
+ sort_order: z.number().int().min(0).optional().describe('Sort order'),
2692
+ is_active: z.boolean().optional().describe('Active status'),
2693
+ }, async ({ id, ...rest }) => {
2694
+ try {
2695
+ const result = await client.update('service-areas', id, buildBody(rest));
2696
+ return formatResult(result);
2697
+ }
2698
+ catch (err) {
2699
+ return formatError(err);
2700
+ }
2701
+ });
2702
+ server.tool('delete_service_area', 'Delete a service area by ID. WARNING: This also deletes all linked measures, measurements, and junction records. Requires service_areas:write scope.', { id: z.string().uuid().describe('Service area ID') }, async ({ id }) => {
2703
+ try {
2704
+ const result = await client.remove('service-areas', id);
2705
+ return formatResult(result);
2706
+ }
2707
+ catch (err) {
2708
+ return formatError(err);
2709
+ }
2710
+ });
2711
+ // ============================================================
2712
+ // Level of Service: Service Area System Classes (scope: service_areas)
2713
+ // ============================================================
2714
+ server.tool('create_service_area_system_class', 'Link a system class to a service area. Requires service_areas:write scope. Resolve IDs first: list_service_areas → service_area_id, list_system_classes → system_class_id.', {
2715
+ service_area_id: z.string().uuid().describe('Service area ID (required)'),
2716
+ system_class_id: z.string().uuid().describe('System class ID (required)'),
2717
+ }, async (params) => {
2718
+ try {
2719
+ const result = await client.create('service-area-system-classes', buildBody(params));
2720
+ return formatResult(result);
2721
+ }
2722
+ catch (err) {
2723
+ return formatError(err);
2724
+ }
2725
+ });
2726
+ server.tool('delete_service_area_system_class', 'Remove a system class link from a service area. Requires service_areas:write scope.', { id: z.string().uuid().describe('Service area system class link ID') }, async ({ id }) => {
2727
+ try {
2728
+ const result = await client.remove('service-area-system-classes', id);
2729
+ return formatResult(result);
2730
+ }
2731
+ catch (err) {
2732
+ return formatError(err);
2733
+ }
2734
+ });
2735
+ // ============================================================
2736
+ // Level of Service: Service Area Sites (scope: service_areas)
2737
+ // ============================================================
2738
+ server.tool('create_service_area_site', 'Link a site to a service area (optional scoping). Requires service_areas:write scope. Resolve IDs first: list_service_areas → service_area_id, list_sites → site_id.', {
2739
+ service_area_id: z.string().uuid().describe('Service area ID (required)'),
2740
+ site_id: z.string().uuid().describe('Site ID (required)'),
2741
+ }, async (params) => {
2742
+ try {
2743
+ const result = await client.create('service-area-sites', buildBody(params));
2744
+ return formatResult(result);
2745
+ }
2746
+ catch (err) {
2747
+ return formatError(err);
2748
+ }
2749
+ });
2750
+ server.tool('delete_service_area_site', 'Remove a site link from a service area. Requires service_areas:write scope.', { id: z.string().uuid().describe('Service area site link ID') }, async ({ id }) => {
2751
+ try {
2752
+ const result = await client.remove('service-area-sites', id);
2753
+ return formatResult(result);
2754
+ }
2755
+ catch (err) {
2756
+ return formatError(err);
2757
+ }
2758
+ });
2759
+ // ============================================================
2760
+ // Level of Service: Measures (scope: los_measures)
2761
+ // ============================================================
2762
+ server.tool('create_los_measure', 'Create a new LoS measure within a service area. Requires los_measures:write scope. Resolve service_area_id first via list_service_areas.', {
2763
+ service_area_id: z.string().uuid().describe('Service area ID (required)'),
2764
+ name: z.string().min(1).max(500).describe('Measure name (required, unique per service area)'),
2765
+ category: z.enum(['quality', 'reliability', 'responsiveness', 'safety', 'sustainability', 'cost_efficiency', 'capacity']).describe('Measure category (required)'),
2766
+ type: z.enum(['community', 'technical']).describe('Measure type (required)'),
2767
+ data_source: z.enum([
2768
+ 'manual', 'custom_formula', 'asset_condition_avg', 'asset_condition_pct_above',
2769
+ 'asset_condition_pct_below', 'risk_score_avg', 'risk_pct_critical',
2770
+ 'wo_response_time_avg', 'wo_completion_time_avg', 'wo_backlog_count', 'wo_overdue_count',
2771
+ 'pm_compliance_rate', 'compliance_score', 'fci', 'deferred_maintenance_ratio',
2772
+ 'asset_past_useful_life_pct',
2773
+ ]).describe('Data source type (required). Use "manual" if values will be entered by hand.'),
2774
+ description: z.string().max(2000).optional().describe('Description'),
2775
+ community_statement: z.string().max(2000).optional().describe('Community-facing statement (for community type measures)'),
2776
+ unit: z.string().max(100).optional().describe('Unit of measurement (e.g., "%", "hours", "count")'),
2777
+ trend_direction: z.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal']).optional().describe('Which direction is better'),
2778
+ target_value: z.number().optional().describe('Target value'),
2779
+ minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
2780
+ stretch_goal: z.number().optional().describe('Stretch goal value'),
2781
+ weight: z.number().min(0).optional().describe('Weight for composite score calculation (default: 1.0)'),
2782
+ data_source_config: z.record(z.unknown()).optional().describe('Data source configuration (JSONB). E.g., {"threshold": 3} for pct_above/below, {"days_back": 90} for WO metrics.'),
2783
+ is_active: z.boolean().optional().describe('Whether the measure is active (default: true)'),
2784
+ sort_order: z.number().int().min(0).optional().describe('Sort order for display'),
2785
+ }, async (params) => {
2786
+ try {
2787
+ const result = await client.create('los-measures', buildBody(params));
2788
+ return formatResult(result);
2789
+ }
2790
+ catch (err) {
2791
+ return formatError(err);
2792
+ }
2793
+ });
2794
+ server.tool('update_los_measure', 'Update an existing LoS measure by ID. Requires los_measures:write scope.', {
2795
+ id: z.string().uuid().describe('LoS measure ID'),
2796
+ name: z.string().min(1).max(500).optional().describe('Measure name'),
2797
+ category: z.enum(['quality', 'reliability', 'responsiveness', 'safety', 'sustainability', 'cost_efficiency', 'capacity']).optional().describe('Measure category'),
2798
+ type: z.enum(['community', 'technical']).optional().describe('Measure type'),
2799
+ data_source: z.enum([
2800
+ 'manual', 'custom_formula', 'asset_condition_avg', 'asset_condition_pct_above',
2801
+ 'asset_condition_pct_below', 'risk_score_avg', 'risk_pct_critical',
2802
+ 'wo_response_time_avg', 'wo_completion_time_avg', 'wo_backlog_count', 'wo_overdue_count',
2803
+ 'pm_compliance_rate', 'compliance_score', 'fci', 'deferred_maintenance_ratio',
2804
+ 'asset_past_useful_life_pct',
2805
+ ]).optional().describe('Data source type'),
2806
+ description: z.string().max(2000).optional().describe('Description'),
2807
+ community_statement: z.string().max(2000).optional().describe('Community-facing statement'),
2808
+ unit: z.string().max(100).optional().describe('Unit of measurement'),
2809
+ trend_direction: z.enum(['higher_is_better', 'lower_is_better', 'target_is_optimal']).optional().describe('Which direction is better'),
2810
+ target_value: z.number().optional().describe('Target value'),
2811
+ minimum_acceptable: z.number().optional().describe('Minimum acceptable value'),
2812
+ stretch_goal: z.number().optional().describe('Stretch goal value'),
2813
+ weight: z.number().min(0).optional().describe('Weight for composite score calculation'),
2814
+ data_source_config: z.record(z.unknown()).optional().describe('Data source configuration (JSONB)'),
2815
+ is_active: z.boolean().optional().describe('Active status'),
2816
+ sort_order: z.number().int().min(0).optional().describe('Sort order'),
2817
+ }, async ({ id, ...rest }) => {
2818
+ try {
2819
+ const result = await client.update('los-measures', id, buildBody(rest));
2820
+ return formatResult(result);
2821
+ }
2822
+ catch (err) {
2823
+ return formatError(err);
2824
+ }
2825
+ });
2826
+ server.tool('delete_los_measure', 'Delete a LoS measure by ID. WARNING: This also deletes all associated measurements and targets history. Requires los_measures:write scope.', { id: z.string().uuid().describe('LoS measure ID') }, async ({ id }) => {
2827
+ try {
2828
+ const result = await client.remove('los-measures', id);
2829
+ return formatResult(result);
2830
+ }
2831
+ catch (err) {
2832
+ return formatError(err);
2833
+ }
2834
+ });
2835
+ // ============================================================
2836
+ // Level of Service: Measurements (scope: los_measurements)
2837
+ // ============================================================
2838
+ server.tool('create_los_measurement', 'Record a new LoS measurement value. Requires los_measurements:write scope. Resolve los_measure_id first via list_los_measures.', {
2839
+ los_measure_id: z.string().uuid().describe('LoS measure ID (required)'),
2840
+ period_type: z.enum(['monthly', 'quarterly', 'semi_annual', 'annual']).describe('Period type (required)'),
2841
+ period_start: z.string().describe('Period start date (ISO 8601, required, e.g., "2026-01-01")'),
2842
+ period_end: z.string().describe('Period end date (ISO 8601, required, e.g., "2026-03-31")'),
2843
+ actual_value: z.number().describe('Measured value (required)'),
2844
+ notes: z.string().max(2000).optional().describe('Notes or context for this measurement'),
2845
+ is_auto: z.boolean().optional().describe('Whether this is an auto-calculated value (default: false)'),
2846
+ }, async (params) => {
2847
+ try {
2848
+ const result = await client.create('los-measurements', buildBody(params));
2849
+ return formatResult(result);
2850
+ }
2851
+ catch (err) {
2852
+ return formatError(err);
2853
+ }
2854
+ });
2855
+ server.tool('update_los_measurement', 'Update an existing LoS measurement by ID. Requires los_measurements:write scope.', {
2856
+ id: z.string().uuid().describe('LoS measurement ID'),
2857
+ actual_value: z.number().optional().describe('Measured value'),
2858
+ notes: z.string().max(2000).optional().describe('Notes or context'),
2859
+ period_type: z.enum(['monthly', 'quarterly', 'semi_annual', 'annual']).optional().describe('Period type'),
2860
+ period_start: z.string().optional().describe('Period start date (ISO 8601)'),
2861
+ period_end: z.string().optional().describe('Period end date (ISO 8601)'),
2862
+ is_auto: z.boolean().optional().describe('Whether this is auto-calculated'),
2863
+ }, async ({ id, ...rest }) => {
2864
+ try {
2865
+ const result = await client.update('los-measurements', id, buildBody(rest));
2866
+ return formatResult(result);
2867
+ }
2868
+ catch (err) {
2869
+ return formatError(err);
2870
+ }
2871
+ });
2872
+ server.tool('delete_los_measurement', 'Delete a LoS measurement by ID. Requires los_measurements:write scope.', { id: z.string().uuid().describe('LoS measurement ID') }, async ({ id }) => {
2873
+ try {
2874
+ const result = await client.remove('los-measurements', id);
2875
+ return formatResult(result);
2876
+ }
2877
+ catch (err) {
2878
+ return formatError(err);
2879
+ }
2880
+ });
2881
+ // ============================================================
2667
2882
  // Bulk operations
2668
2883
  // ============================================================
2669
2884
  const BULK_RESOURCES = [
@@ -2685,6 +2900,7 @@ export function registerWriteTools(server, client) {
2685
2900
  'custom-field-definitions', 'custom-field-values',
2686
2901
  'vendor-site-assignments', 'contract-sites',
2687
2902
  'asset-documents', 'attachments', 'project-documents', 'contract-documents',
2903
+ 'service-areas', 'los-measures', 'los-measurements',
2688
2904
  ];
2689
2905
  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.', {
2690
2906
  resource: z.enum(BULK_RESOURCES).describe('Resource type (e.g. "assets", "work-orders")'),