Skip to content

Add Azure IoT Hub device and query read tools - #3180

Open
Iris20050110 wants to merge 5 commits into
mainfrom
iothub-readtools
Open

Add Azure IoT Hub device and query read tools#3180
Iris20050110 wants to merge 5 commits into
mainfrom
iothub-readtools

Conversation

@Iris20050110

@Iris20050110 Iris20050110 commented Jul 29, 2026

Copy link
Copy Markdown

Adds six read-only IoT Hub commands: device show, device stats, device twin get, query run, query compile, and query discover. Includes options, IIoTHubDeviceService/IoTHubDeviceService, query compiler and field discoverer, models, JSON context, setup registration, GetIoTHubKeys on IIoTHubService, package references, command docs, e2e prompts, consolidated tool mapping, changelog entry, and unit tests.

What does this PR do?

[Provide a clear, concise description of the changes]
Azure IoT Hub to Azure MCP Server
This is a read-only tool set which includes commands device show, twin get, device stats, and query run that reads devices registry and twin APIs in Iot Hub. The documentation will be published at https://learn.microsoft.com/en-us/cli/azure/iot/hub?view=azure-cli-latest#az-iot-hub-show

[Add additional context, screenshots, or information that helps reviewers]

  1. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Show the device identity for a device in an IoT Hub device registry. Returns device identity metadata while intentionally excluding authentication keys. Device names/IDs are case-sensitive and must match exactly." --prompt "Show device 'device1' in IoT Hub 'hub1' in resource group 'rg1'" --prompt "Get the device identity for 'sensor-42' registered in IoT Hub 'hub1'" --prompt "Show me details of device 'device1' in my IoT Hub 'hub1'"
  2. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Get device statistics for an IoT Hub identity registry. Returns aggregate device counts for the hub: disabledDeviceCount (the number of currently disabled devices), enabledDeviceCount (the number of currently enabled devices), and totalDeviceCount (the total number of devices registered for the IoT Hub). Hub names/IDs are case-sensitive and must match exactly." --prompt "How many devices are registered in IoT Hub 'hub1'?" --prompt "Show device statistics for IoT Hub 'hub1' in resource group 'rg1'" --prompt "Get enabled, disabled, and total device counts for IoT Hub 'hub1'"
  3. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Get a device twin from an IoT Hub device registry. Returns the device twin document including tags and desired/reported properties. Device names/IDs are case-sensitive and must match exactly." --prompt "Get the device twin for device 'device1' in IoT Hub 'hub1'" --prompt "Show desired and reported properties for device 'sensor-42' in IoT Hub 'hub1'" --prompt "Retrieve the twin tags for device 'device1' in hub 'hub1' resource group 'rg1'"
  4. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Run an IoT Hub SQL-like query against the device registry and return a single page of results. Prefer projecting only the specific property fields you need; avoid 'SELECT *' unless the user explicitly asks for raw device twins, all fields, modules, jobs, or full JSON. Use --max-count to set the page size (default 100, maximum 100). Values greater than 100 are capped at 100, so one page is always at most 100 items. Never make repeated calls or loop for additional pages in a single user request. Return exactly one page and, when hasMore is true, include the continuationToken for a later explicit next-page request. The --continuation-token input must be the opaque continuationToken string returned by a previous iothub_query_run response; do not pass hasMore=true/false or any boolean value." --prompt "Run an IoT Hub SQL query on 'hub1' to get device IDs where status is enabled" --prompt "Query IoT Hub 'hub1' for devices where the location tag is building5" --prompt "List connected devices in IoT Hub 'hub1' using an IoT Hub query"
  5. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Compile a structured set of typed predicates into a syntactically valid IoT Hub query string. Instead of writing raw IoT Hub SQL, supply a JSON array of predicates via --filters where each predicate specifies a 'scope' (device, tags, desired, or reported), a 'field' (the property name/path within that scope), an 'operator' (equals, notEquals, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual), and a 'value' (string, number, or boolean). The compiler maps each predicate to the correct field path, validates it, and joins the predicates with --logical-operator (AND by default). Pass the 'fields' object returned by 'iothub query discover' to --discovered-fields to reject filters that reference paths not found in sampled device twins. Returns an object with a single 'query' string that can be passed directly to 'iothub query run'. This command performs no network calls." --prompt "Compile these device twin filter predicates into an IoT Hub query string" --prompt "Build a valid IoT Hub query from typed filters without writing raw SQL" --prompt "Turn scope/field/operator/value predicates into an IoT Hub query string"
  6. dotnet run --project eng/tools/ToolDescriptionEvaluator/src -- --test-single-tool --tool-description "Discover queryable IoT Hub device twin field paths by sampling a small page of devices with 'SELECT * FROM devices'. Use this before 'iothub query compile' when the user asks for fields whose exact twin path is unknown. The command returns compact field paths grouped by scope: device, tags, desired, and reported, with observed JSON types and example values. Pass the returned 'fields' object to 'iothub query compile --discovered-fields' so compile can reject nonexistent fields and suggest matching nested paths. Defaults to sampling 10 devices; --max-count controls the sample size and is capped at 100." --prompt "Discover the queryable device twin fields in IoT Hub 'hub1'" --prompt "What device twin field paths can I query in IoT Hub 'hub1'?" --prompt "Sample devices in IoT Hub 'hub1' to find available twin properties for querying"
image

GitHub issue number?

[Link to the GitHub issue this PR addresses]
#2917

Pre-merge Checklist

  • Required for All PRs
    • Read contribution guidelines
    • PR title clearly describes the change
    • Commit history is clean with descriptive messages (cleanup guide)
    • Added comprehensive tests for new/modified functionality
    • Created a changelog entry if the change falls among the following: new feature, bug fix, UI/UX update, breaking change, or updated dependencies. Follow the changelog entry guide
  • For MCP tool changes:
    • One tool per PR: This PR adds or modifies only one MCP tool for faster review cycles
    • Updated servers/Azure.Mcp.Server/README.md and/or servers/Fabric.Mcp.Server/README.md documentation
    • Validate README.md changes running the script ./eng/scripts/Process-PackageReadMe.ps1. See Package README
    • For new or modified tool descriptions, ran ToolDescriptionEvaluator and obtained a score of 0.4 or more and a top 3 ranking for all related test prompts
    • For tools with new names, including new tools or renamed tools, update consolidated-tools.json
    • For renamed tools, follow the Tool Rename Checklist and tag the PR with the breaking-change label
    • For new tools associated with Azure services or publicly available tools/APIs/products, add URL to documentation in the PR description
  • Extra steps for Azure MCP Server tool changes:
    • Updated command list in servers/Azure.Mcp.Server/docs/azmcp-commands.md
    • Ran ./eng/scripts/Update-AzCommandsMetadata.ps1 to update tool metadata in azmcp-commands.md (required for CI)
    • Updated test prompts in servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
    • 👉 For Community (non-Microsoft team member) PRs:
      • Security review: Reviewed code for security vulnerabilities, malicious code, or suspicious activities before running tests (crypto mining, spam, data exfiltration, etc.)
      • Manual tests run: added comment /azp run mcp - pullrequest - live to run Live Test Pipeline

Adds six read-only IoT Hub commands: device show, device stats, device twin get, query run, query compile, and query discover. Includes options, IIoTHubDeviceService/IoTHubDeviceService, query compiler and field discoverer, models, JSON context, setup registration, GetIoTHubKeys on IIoTHubService, package references, command docs, e2e prompts, consolidated tool mapping, changelog entry, and unit tests.
Copilot AI review requested due to automatic review settings July 29, 2026 17:18
@Iris20050110
Iris20050110 requested review from a team as code owners July 29, 2026 17:18
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new set of read-only Azure IoT Hub “device registry + query” tools to the Azure MCP server, including data-plane calls for device identity/twin/statistics and a local query compile/discover workflow to help agents construct valid IoT Hub queries.

Changes:

  • Added IoT Hub device registry commands (show, stats, twin get) and IoT Hub query commands (run, compile, discover), plus DI registration and JSON source-gen updates.
  • Introduced a new IIoTHubDeviceService/IoTHubDeviceService that resolves hub hostname + SAS keys via ARM, then calls IoT Hub REST endpoints for device/twin/query/statistics.
  • Updated documentation, consolidated tool mapping, changelog entry, and added unit tests for the new commands.

Reviewed changes

Copilot reviewed 47 out of 47 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Query/IoTHubQueryRunCommandTests.cs Adds unit tests for the query run command behavior and validation.
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Query/IoTHubQueryDiscoverCommandTests.cs Adds unit tests for query field discovery command behavior and error handling.
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Query/IoTHubQueryCompileCommandTests.cs Adds unit tests for compiling structured filters into IoT Hub query strings.
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Device/IoTHubDeviceTwinGetCommandTests.cs Adds unit tests for retrieving device twins from IoT Hub.
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Device/IoTHubDeviceStatisticsCommandTests.cs Adds unit tests for device registry statistics retrieval.
tools/Azure.Mcp.Tools.IoTHub/tests/Azure.Mcp.Tools.IoTHub.UnitTests/Device/IoTHubDeviceShowCommandTests.cs Adds unit tests for device identity retrieval.
tools/Azure.Mcp.Tools.IoTHub/src/Services/IoTHubService.cs Extends ARM-based IoT Hub service to also list IoT Hub SAS keys.
tools/Azure.Mcp.Tools.IoTHub/src/Services/IoTHubDeviceService.cs Implements data-plane IoT Hub REST calls (device/twin/query/stats) with caching + timeouts.
tools/Azure.Mcp.Tools.IoTHub/src/Services/IIoTHubService.cs Adds a service contract for retrieving IoT Hub SAS keys.
tools/Azure.Mcp.Tools.IoTHub/src/Services/IIoTHubDeviceService.cs Introduces new service contract for device registry and query operations.
tools/Azure.Mcp.Tools.IoTHub/src/Query/IoTHubQueryFieldDiscoverer.cs Adds logic to discover queryable fields/types/examples from sampled twin documents.
tools/Azure.Mcp.Tools.IoTHub/src/Query/IoTHubQueryCompiler.cs Adds a structured predicate compiler to generate valid IoT Hub query strings.
tools/Azure.Mcp.Tools.IoTHub/src/Options/Query/IoTHubQueryRunOptions.cs Adds options for running queries (query, max count, continuation token, hub, RG, etc.).
tools/Azure.Mcp.Tools.IoTHub/src/Options/Query/IoTHubQueryDiscoverOptions.cs Adds options for field discovery sampling (hub, RG, max count, etc.).
tools/Azure.Mcp.Tools.IoTHub/src/Options/Query/IoTHubQueryCompileOptions.cs Adds options for local query compilation from JSON filters (+ optional discovered fields).
tools/Azure.Mcp.Tools.IoTHub/src/Options/Device/IoTHubDeviceTwinGetOptions.cs Adds options for device twin get (device id, hub, RG, subscription/tenant).
tools/Azure.Mcp.Tools.IoTHub/src/Options/Device/IoTHubDeviceStatisticsOptions.cs Adds options for device statistics (hub, RG, subscription/tenant).
tools/Azure.Mcp.Tools.IoTHub/src/Options/Device/IoTHubDeviceShowOptions.cs Adds options for device show (device id, hub, RG, subscription/tenant).
tools/Azure.Mcp.Tools.IoTHub/src/Models/RegistryStatistics.cs Adds model for IoT Hub registry statistics response.
tools/Azure.Mcp.Tools.IoTHub/src/Models/QueryPredicate.cs Adds structured predicate model for query compilation.
tools/Azure.Mcp.Tools.IoTHub/src/Models/QueryDiscoveredFields.cs Adds model for discovered fields grouped by scope.
tools/Azure.Mcp.Tools.IoTHub/src/Models/QueryDiscoveredField.cs Adds model for a single discovered field (path/type/examples).
tools/Azure.Mcp.Tools.IoTHub/src/Models/QueryCompileRequest.cs Adds model representing a full compile request (filters/from/top/logical op/discovered fields).
tools/Azure.Mcp.Tools.IoTHub/src/Models/PredicateScope.cs Adds enum for predicate scope (device/tags/desired/reported).
tools/Azure.Mcp.Tools.IoTHub/src/Models/PredicateOperator.cs Adds enum for predicate operators.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubQueryRunResult.cs Adds result model for query run (items/count/hasMore/continuationToken/message).
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubQueryRequest.cs Adds request model for IoT Hub query endpoint.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubQueryPage.cs Adds internal page model for query results + continuation.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubQueryDiscoverResult.cs Adds result model for discovered fields + sampling metadata.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubQueryCompileResult.cs Adds result model for compiled query + maxCount hint.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubKey.cs Adds internal model for IoT Hub SAS keys retrieved from ARM.
tools/Azure.Mcp.Tools.IoTHub/src/Models/IoTHubDeviceCountStats.cs Adds model for device-count metric stats (snapshot/peak/average).
tools/Azure.Mcp.Tools.IoTHub/src/Models/DeviceTwin.cs Adds models for device identity/twin (and related nested types).
tools/Azure.Mcp.Tools.IoTHub/src/IoTHubSetup.cs Registers new services and command groups/subcommands for iothub device/twin/query.
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Query/IoTHubQueryRunCommand.cs Implements query run command (single-page behavior, max-count cap, continuation token validation).
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Query/IoTHubQueryDiscoverCommand.cs Implements query discover command (sample + field discovery).
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Query/IoTHubQueryCompileCommand.cs Implements local query compile command (JSON parsing + validation + compilation).
tools/Azure.Mcp.Tools.IoTHub/src/Commands/IoTHubJsonContext.cs Expands JSON source-generation registrations for new models/results.
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Device/IoTHubDeviceTwinGetCommand.cs Implements device twin get command.
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Device/IoTHubDeviceStatisticsCommand.cs Implements device statistics command.
tools/Azure.Mcp.Tools.IoTHub/src/Commands/Device/IoTHubDeviceShowCommand.cs Implements device show command and its metadata/description.
tools/Azure.Mcp.Tools.IoTHub/src/Azure.Mcp.Tools.IoTHub.csproj Adds the Azure.ResourceManager.IotHub package reference.
servers/Azure.Mcp.Server/src/Resources/consolidated-tools.json Adds a consolidated “get_azure_iot_hub_devices” tool mapping to the new commands.
servers/Azure.Mcp.Server/docs/e2eTestPrompts.md Adds e2e prompts for the new IoT Hub device and query commands.
servers/Azure.Mcp.Server/docs/azmcp-commands.md Adds CLI documentation entries for the new IoT Hub commands.
servers/Azure.Mcp.Server/changelog-entries/Iris20050110-iothub-read-tools.yaml Adds changelog entry describing the new IoT Hub read tools.
Directory.Packages.props Adds central package version for Azure.ResourceManager.IotHub.
Comments suppressed due to low confidence (2)

tools/Azure.Mcp.Tools.IoTHub/src/Services/IoTHubDeviceService.cs:174

  • HttpResponseMessage should be disposed to promptly release the underlying connection/resources. This method currently keeps the response undisposed, unlike RunQuery which disposes its response.
    tools/Azure.Mcp.Tools.IoTHub/src/Services/IoTHubDeviceService.cs:297
  • HttpResponseMessage should be disposed to promptly release the underlying connection/resources. This method currently keeps the response undisposed after reading, which can increase socket pressure under repeated use.

return Task.FromResult(context.Response);
}

var result = new IoTHubQueryCompileResult(query, options.Top);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied the cap. But instead of hardcoding 100, I promoted run's limit to a shared MaxPageSize constant and referenced it from both compile and run

Comment on lines +108 to +127
public void Add(string field, string type, JsonElement example)
{
if (string.IsNullOrWhiteSpace(field))
{
return;
}

if (!_fields.TryGetValue(field, out var state))
{
state = new FieldState(field);
_fields.Add(field, state);
}

state.Add(type, example);
}

public List<QueryDiscoveredField> ToFields() => _fields.Values
.Select(state => new QueryDiscoveredField(state.Field, state.Type, state.Examples))
.ToList();
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied

Comment on lines +24 to +28
public record TwinProperties(
[property: JsonPropertyName("desired")] JsonElement? Desired,
[property: JsonPropertyName("reported")] JsonElement? Reported);

public record DeviceCapabilities(

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied

Comment thread tools/Azure.Mcp.Tools.IoTHub/src/Services/IoTHubDeviceService.cs Outdated
Iris20050110 and others added 4 commits July 29, 2026 13:09
Update device show description

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Add shared IoTHubQueryLimits.MaxPageSize and reference it from query run and compile so the compile page-size hint cannot exceed the run cap
- Split DeviceTwin.cs into one type per file (DeviceIdentity, DeviceAuthentication, DeviceCapabilities, TwinProperties, TwinPatch, TwinPatchProperties)
- Dispose HttpResponseMessage in IoTHubDeviceService GET calls (GetDevice, GetDeviceTwin, GetDeviceStatistics)
- Route discover/compile field-path validation through shared IoTHubQueryCompiler.IsValidFieldPath so discover never emits a path compile would reject; add unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants