diff --git a/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts b/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts new file mode 100644 index 00000000..994ab0be --- /dev/null +++ b/src/McpServer/autopilot-monitor-mcp/src/resource-catalog.ts @@ -0,0 +1,117 @@ +/** + * Source-of-truth for the static resource catalogs (event types, device + * properties). Consumed by both `resources.ts` (MCP-protocol resources) + * and the `get_resource` tool (workaround for stateless-HTTP MCP clients + * whose resource discovery is broken). + */ + +export const EVENT_TYPES_CATALOG = { + phase_events: [ + 'phase_transition', + 'esp_state_change', + 'completion_check', + 'enrollment_complete', + 'enrollment_failed', + 'desktop_arrived', + ], + app_events: [ + 'app_install_started', + 'app_install_completed', + 'app_install_failed', + 'app_download_started', + 'app_install_skipped', + ], + network_events: ['network_state_change', 'network_connectivity_check'], + device_info_events: [ + 'os_info', + 'hardware_spec', + 'tpm_status', + 'autopilot_profile', + 'secureboot_status', + 'bitlocker_status', + 'network_adapters', + 'network_interface_info', + 'aad_join_status', + 'enrollment_type_detected', + ], + error_events: ['error_detected'], + vulnerability_events: ['software_inventory_analysis', 'vulnerability_report'], + other: [ + 'performance_snapshot', + 'log_entry', + 'gather_result', + 'script_completed', + 'script_failed', + 'ime_agent_version', + ], +} as const; + +export const DEVICE_PROPERTIES_CATALOG = { + _usage: { + note: 'Use these keys in the deviceProperties parameter of search_sessions.', + booleanFormat: 'Use "True" or "False" (case-insensitive)', + numericOperators: 'Prefix value with >=, <=, >, or < for range filters (e.g. ">=8")', + arraySearch: 'For array properties, value is matched as substring in any array element', + }, + tpm_status: { + 'tpm_status.specVersion': { type: 'string', description: 'TPM specification version (e.g. "2.0", "1.2")' }, + 'tpm_status.manufacturerName': { type: 'string', description: 'TPM manufacturer (e.g. "INTC", "IFX")' }, + 'tpm_status.manufacturerVersion': { type: 'string', description: 'TPM firmware version' }, + 'tpm_status.isActivated': { type: 'boolean', description: 'TPM activation status' }, + 'tpm_status.isEnabled': { type: 'boolean', description: 'TPM enabled status' }, + 'tpm_status.isOwned': { type: 'boolean', description: 'TPM ownership status' }, + }, + secureboot_status: { + 'secureboot_status.uefiSecureBootEnabled': { type: 'boolean', description: 'Secure Boot enabled' }, + }, + bitlocker_status: { + 'bitlocker_status.systemDriveProtected': { type: 'boolean', description: 'System drive (C:) BitLocker protection' }, + 'bitlocker_status.volumes': { type: 'array', description: 'All BitLocker volumes — search by volume letter or protection status' }, + }, + autopilot_profile: { + 'autopilot_profile.autopilotModeLabel': { type: 'string', description: 'Deployment mode (e.g. "User Driven (0)", "Self-Deploying (1)")' }, + 'autopilot_profile.domainJoinMethodLabel': { type: 'string', description: 'Join method (e.g. "Entra Join", "Hybrid Azure AD Join")' }, + 'autopilot_profile.CloudAssignedEspEnabled': { type: 'boolean', description: 'ESP enabled in Autopilot profile' }, + 'autopilot_profile.CloudAssignedDeviceName': { type: 'string', description: 'Cloud-assigned device name template' }, + 'autopilot_profile.CloudAssignedLanguage': { type: 'string', description: 'Cloud-assigned language/locale' }, + }, + hardware_spec: { + 'hardware_spec.cpuName': { type: 'string', description: 'CPU model name (e.g. "Intel Core i7-1265U")' }, + 'hardware_spec.cpuCores': { type: 'number', description: 'Number of physical CPU cores' }, + 'hardware_spec.cpuLogicalProcessors': { type: 'number', description: 'Number of logical processors' }, + 'hardware_spec.ramTotalGB': { type: 'number', description: 'Total RAM in GB. Use >=N for minimum (e.g. ">=8")' }, + 'hardware_spec.ramSpeed': { type: 'string', description: 'RAM speed (e.g. "3200 MHz")' }, + 'hardware_spec.ramType': { type: 'string', description: 'RAM type (e.g. "DDR4", "LPDDR5")' }, + 'hardware_spec.diskCount': { type: 'number', description: 'Number of physical disks' }, + 'hardware_spec.hasSSD': { type: 'boolean', description: 'Has SSD or NVMe storage (computed)' }, + 'hardware_spec.disks': { type: 'array', description: 'Disk details — search for "NVMe", "SSD", disk model names' }, + 'hardware_spec.systemDriveFreeGB': { type: 'number', description: 'Free space on C: in GB' }, + 'hardware_spec.systemDriveTotalGB': { type: 'number', description: 'Total C: drive size in GB' }, + 'hardware_spec.biosVersion': { type: 'string', description: 'BIOS/UEFI version string' }, + 'hardware_spec.batteryPresent': { type: 'boolean', description: 'Device has a battery' }, + 'hardware_spec.batteryHealthPercent': { type: 'number', description: 'Battery health percentage' }, + 'hardware_spec.gpus': { type: 'array', description: 'GPU details — search for GPU model names' }, + }, + network_interface_info: { + 'network_interface_info.connectionType': { type: 'string', description: 'Connection type: "WiFi" or "Ethernet"' }, + 'network_interface_info.linkSpeedMbps': { type: 'number', description: 'Link speed in Mbps' }, + 'network_interface_info.adapterName': { type: 'string', description: 'Network adapter name' }, + 'network_interface_info.macAddress': { type: 'string', description: 'MAC address of active adapter' }, + }, + aad_join_status: { + 'aad_join_status.joinType': { type: 'string', description: 'Azure AD join type' }, + 'aad_join_status.tenantId': { type: 'string', description: 'Entra ID tenant' }, + 'aad_join_status.deviceId': { type: 'string', description: 'Entra device object ID' }, + }, +} as const; + +export type ResourceName = 'event_types' | 'device_properties'; + +export function getResourceContent(name: ResourceName): unknown { + switch (name) { + case 'event_types': + return EVENT_TYPES_CATALOG; + case 'device_properties': + return DEVICE_PROPERTIES_CATALOG; + } +} diff --git a/src/McpServer/autopilot-monitor-mcp/src/resources.ts b/src/McpServer/autopilot-monitor-mcp/src/resources.ts index 4bc33b5c..aff847e0 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/resources.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/resources.ts @@ -1,5 +1,12 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { EVENT_TYPES_CATALOG, DEVICE_PROPERTIES_CATALOG } from './resource-catalog.js'; +/** + * MCP-protocol resources. Note that some clients (e.g. Claude Code's HTTP-MCP + * bridge in stateless mode) do not expose `resources/list` correctly — for + * those clients, use the `get_resource(name)` tool which returns the same + * data via a regular tool call. + */ export function registerResources(server: McpServer): void { server.resource( 'event_types', @@ -13,50 +20,7 @@ export function registerResources(server: McpServer): void { { uri: 'autopilot://event-types', mimeType: 'application/json', - text: JSON.stringify( - { - phase_events: [ - 'phase_transition', - 'esp_state_change', - 'completion_check', - 'enrollment_complete', - 'enrollment_failed', - 'desktop_arrived', - ], - app_events: [ - 'app_install_started', - 'app_install_completed', - 'app_install_failed', - 'app_download_started', - 'app_install_skipped', - ], - network_events: ['network_state_change', 'network_connectivity_check'], - device_info_events: [ - 'os_info', - 'hardware_spec', - 'tpm_status', - 'autopilot_profile', - 'secureboot_status', - 'bitlocker_status', - 'network_adapters', - 'network_interface_info', - 'aad_join_status', - 'enrollment_type_detected', - ], - error_events: ['error_detected'], - vulnerability_events: ['software_inventory_analysis', 'vulnerability_report'], - other: [ - 'performance_snapshot', - 'log_entry', - 'gather_result', - 'script_completed', - 'script_failed', - 'ime_agent_version', - ], - }, - null, - 2 - ), + text: JSON.stringify(EVENT_TYPES_CATALOG, null, 2), }, ], }) @@ -77,68 +41,7 @@ export function registerResources(server: McpServer): void { { uri: 'autopilot://device-properties', mimeType: 'application/json', - text: JSON.stringify( - { - _usage: { - note: 'Use these keys in the deviceProperties parameter of search_sessions.', - booleanFormat: 'Use "True" or "False" (case-insensitive)', - numericOperators: 'Prefix value with >=, <=, >, or < for range filters (e.g. ">=8")', - arraySearch: 'For array properties, value is matched as substring in any array element', - }, - tpm_status: { - 'tpm_status.specVersion': { type: 'string', description: 'TPM specification version (e.g. "2.0", "1.2")' }, - 'tpm_status.manufacturerName': { type: 'string', description: 'TPM manufacturer (e.g. "INTC", "IFX")' }, - 'tpm_status.manufacturerVersion': { type: 'string', description: 'TPM firmware version' }, - 'tpm_status.isActivated': { type: 'boolean', description: 'TPM activation status' }, - 'tpm_status.isEnabled': { type: 'boolean', description: 'TPM enabled status' }, - 'tpm_status.isOwned': { type: 'boolean', description: 'TPM ownership status' }, - }, - secureboot_status: { - 'secureboot_status.uefiSecureBootEnabled': { type: 'boolean', description: 'Secure Boot enabled' }, - }, - bitlocker_status: { - 'bitlocker_status.systemDriveProtected': { type: 'boolean', description: 'System drive (C:) BitLocker protection' }, - 'bitlocker_status.volumes': { type: 'array', description: 'All BitLocker volumes — search by volume letter or protection status' }, - }, - autopilot_profile: { - 'autopilot_profile.autopilotModeLabel': { type: 'string', description: 'Deployment mode (e.g. "User Driven (0)", "Self-Deploying (1)")' }, - 'autopilot_profile.domainJoinMethodLabel': { type: 'string', description: 'Join method (e.g. "Entra Join", "Hybrid Azure AD Join")' }, - 'autopilot_profile.CloudAssignedEspEnabled': { type: 'boolean', description: 'ESP enabled in Autopilot profile' }, - 'autopilot_profile.CloudAssignedDeviceName': { type: 'string', description: 'Cloud-assigned device name template' }, - 'autopilot_profile.CloudAssignedLanguage': { type: 'string', description: 'Cloud-assigned language/locale' }, - }, - hardware_spec: { - 'hardware_spec.cpuName': { type: 'string', description: 'CPU model name (e.g. "Intel Core i7-1265U")' }, - 'hardware_spec.cpuCores': { type: 'number', description: 'Number of physical CPU cores' }, - 'hardware_spec.cpuLogicalProcessors': { type: 'number', description: 'Number of logical processors' }, - 'hardware_spec.ramTotalGB': { type: 'number', description: 'Total RAM in GB. Use >=N for minimum (e.g. ">=8")' }, - 'hardware_spec.ramSpeed': { type: 'string', description: 'RAM speed (e.g. "3200 MHz")' }, - 'hardware_spec.ramType': { type: 'string', description: 'RAM type (e.g. "DDR4", "LPDDR5")' }, - 'hardware_spec.diskCount': { type: 'number', description: 'Number of physical disks' }, - 'hardware_spec.hasSSD': { type: 'boolean', description: 'Has SSD or NVMe storage (computed)' }, - 'hardware_spec.disks': { type: 'array', description: 'Disk details — search for "NVMe", "SSD", disk model names' }, - 'hardware_spec.systemDriveFreeGB': { type: 'number', description: 'Free space on C: in GB' }, - 'hardware_spec.systemDriveTotalGB': { type: 'number', description: 'Total C: drive size in GB' }, - 'hardware_spec.biosVersion': { type: 'string', description: 'BIOS/UEFI version string' }, - 'hardware_spec.batteryPresent': { type: 'boolean', description: 'Device has a battery' }, - 'hardware_spec.batteryHealthPercent': { type: 'number', description: 'Battery health percentage' }, - 'hardware_spec.gpus': { type: 'array', description: 'GPU details — search for GPU model names' }, - }, - network_interface_info: { - 'network_interface_info.connectionType': { type: 'string', description: 'Connection type: "WiFi" or "Ethernet"' }, - 'network_interface_info.linkSpeedMbps': { type: 'number', description: 'Link speed in Mbps' }, - 'network_interface_info.adapterName': { type: 'string', description: 'Network adapter name' }, - 'network_interface_info.macAddress': { type: 'string', description: 'MAC address of active adapter' }, - }, - aad_join_status: { - 'aad_join_status.joinType': { type: 'string', description: 'Azure AD join type' }, - 'aad_join_status.tenantId': { type: 'string', description: 'Entra ID tenant' }, - 'aad_join_status.deviceId': { type: 'string', description: 'Entra device object ID' }, - }, - }, - null, - 2 - ), + text: JSON.stringify(DEVICE_PROPERTIES_CATALOG, null, 2), }, ], }) diff --git a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts index 4c56aec8..7d82fee8 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts @@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { apiFetch, buildQuery, followNextLink, pickGlobalOrTenantPath } from '../client.js'; import { withToolTelemetry } from '../telemetry.js'; +import { getResourceContent } from '../resource-catalog.js'; import { READ_ONLY, READ_ONLY_OPEN, MAX_RESULT_SIZE_CHARS, toolResultText } from './shared.js'; import { toolError } from './error-handler.js'; @@ -519,7 +520,10 @@ export function registerAdminTools(server: McpServer): void { 'Get rule firing statistics for analyze and gather rules. Shows which rules fire most often, ' + 'their hit rates (fires/evaluations), and daily trends. Use to identify commonly triggered rules, ' + 'optimize rule definitions, or understand tenant-specific failure patterns. ' + - 'Without tenantId returns global stats (cross-tenant). With tenantId returns tenant-specific stats.', + 'Without tenantId returns global stats (cross-tenant). With tenantId returns tenant-specific stats. ' + + 'NOTE: default window is 30 days × every rule × per-day trend rows — for 20+ rules the response ' + + 'easily exceeds 70 KB and can trip the response cap. Pass a tighter `startDate`/`endDate` window ' + + '(7 days is usually plenty) and/or `ruleType` filter to keep responses lean.', { tenantId: z.string().optional().describe('Filter by tenant ID. Omit for global (cross-tenant) stats.'), ruleType: z.enum(['analyze', 'gather']).optional().describe('Filter by rule type'), @@ -544,4 +548,28 @@ export function registerAdminTools(server: McpServer): void { } }) ); + + // Tool: get_resource — returns the same content as the MCP `resources` would, + // but via a regular tool call. Workaround for clients (e.g. Claude Code's HTTP-MCP + // bridge in stateless mode) that don't expose `resources/list` reliably. + server.tool( + 'get_resource', + 'Returns the contents of a named static catalog resource. Use this when ' + + 'the host MCP client cannot list/read MCP-protocol resources (common with ' + + 'stateless HTTP MCP servers). Available names:\n' + + ' - "event_types": catalog of valid eventType strings for search_sessions_by_event\n' + + ' - "device_properties": catalog of dot-notation keys for the deviceProperties filter on search_sessions', + { + name: z.enum(['event_types', 'device_properties']).describe('Resource name'), + }, + READ_ONLY_OPEN, + async (args) => withToolTelemetry('get_resource', async () => { + try { + const data = getResourceContent(args.name); + return toolResultText(data, MAX_RESULT_SIZE_CHARS.small); + } catch (error: unknown) { + return toolError('get_resource', args, error); + } + }) + ); } diff --git a/src/McpServer/autopilot-monitor-mcp/src/tools/sessions.ts b/src/McpServer/autopilot-monitor-mcp/src/tools/sessions.ts index 1bae928b..35947fc5 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/tools/sessions.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/tools/sessions.ts @@ -38,7 +38,7 @@ export function registerSessionTools(server: McpServer): void { 'Search enrollment sessions. Omit tenantId for cross-tenant search (Global Admin), or specify tenantId for single-tenant. ' + 'Basic properties (status, serial number, manufacturer, model, etc.) filter on the session index. ' + 'Use deviceProperties for ANY device hardware/config filter — keys use "eventType.propertyName" notation. ' + - 'Consult the device_properties resource for available keys. ' + + 'Consult the device_properties catalog (call get_resource(name="device_properties")) for available keys. ' + 'Examples: {"tpm_status.specVersion": "2.0"}, {"hardware_spec.ramTotalGB": ">=8"}, {"secureboot_status.uefiSecureBootEnabled": "True"}. ' + 'Array values are searched as substring match (e.g. disks containing "NVMe"). ' + 'For COUNTING / AGGREGATION queries (e.g. "how many V2 enrollments?", "how many failed in last 7 days?") ALWAYS pass ' + @@ -78,7 +78,7 @@ export function registerSessionTools(server: McpServer): void { 'isUserDriven, isHybridJoin, agentVersion, imeAgentVersion, geoCountry.'), deviceProperties: z.record(z.string(), z.string()).optional().describe( 'Dynamic device property filters. Keys use "eventType.propertyName" dot notation. ' + - 'See the device_properties resource for all available keys and types. ' + + 'See the device_properties catalog (call get_resource(name="device_properties")) for all available keys and types. ' + 'Values: exact match by default. Prefix with >=, <=, >, < for numeric ranges (e.g. ">=8"). ' + 'Booleans: use "True" or "False". Arrays: substring match in any element.' ), @@ -116,14 +116,14 @@ export function registerSessionTools(server: McpServer): void { server.tool( 'search_sessions_by_event', 'Find sessions that contain a specific event type (e.g. app install failure, phase transitions, errors). ' + - 'Omit tenantId for cross-tenant search (Global Admin). Check the event_types resource for valid eventType values. ' + + 'Omit tenantId for cross-tenant search (Global Admin). Check the event_types catalog (call get_resource(name="event_types")) for valid eventType values. ' + 'Use this to answer: which devices had a failed Teams install, which sessions had an error in DeviceSetup phase. ' + 'This endpoint is fully paginated — there is no truncation. The default pageSize=200 is tuned for typical ' + 'interactive queries; raise it (up to 1000) for full sweeps. For broad analysis, use pageSize=1000 and follow ' + 'nextLink repeatedly until absent. Pass the whole nextLink string as "continuation" so all backend-echoed query ' + 'params round-trip correctly.', { - eventType: z.string().describe('Event type string — see event_types resource for valid values (e.g. "app_install_failed", "enrollment_failed")'), + eventType: z.string().describe('Event type string — see event_types catalog (call get_resource(name="event_types")) for valid values (e.g. "app_install_failed", "enrollment_failed")'), tenantId: z.string().optional().describe('Tenant ID. Omit for cross-tenant search (Global Admin only).'), pageSize: z.coerce.number().int().min(1).max(1000).optional().default(200) .describe('Page size (1-1000, default 200). Returns this many sessions per call; follow nextLink to fetch more.'),