-
Notifications
You must be signed in to change notification settings - Fork 537
Add Azure ManagedCleanroom toolset with first commands: collaborations list and collaboration create #2882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add Azure ManagedCleanroom toolset with first commands: collaborations list and collaboration create #2882
Changes from 5 commits
6978d33
0d431c4
1b4eb28
b3aef3a
3b62db7
e567734
10135b1
6608cb5
043e680
8c6fd88
b145dd5
eb54a81
b3b64b8
c583bf2
471c9e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
ShreyaSangwa marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| changes: | ||
| - section: "New Features" | ||
| description: "Added Azure Managed Cleanroom toolset with two commands for interacting with Azure Cleanroom: `managedcleanroom collaborations list` and `managedcleanroom collaboration create`." |
|
ShreyaSangwa marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # Azure Managed Cleanroom MCP Toolset - Architecture | ||
|
ShreyaSangwa marked this conversation as resolved.
|
||
|
|
||
| ## Overview | ||
|
|
||
| `Azure.Mcp.Tools.ManagedCleanroom` provides comprehensive operations for interacting with Azure Managed Cleanroom services. Commands are organized into logical groups for managing collaborations, analytics workloads, OIDC configuration, datasets, queries, consent documents, and audit events. | ||
|
ShreyaSangwa marked this conversation as resolved.
|
||
|
|
||
| Commands interact with: | ||
| - **Data Plane APIs**: Cleanroom Analytics Frontend for read operations (list collaborations, queries, datasets) | ||
| - **Control Plane APIs**: Azure Resource Manager (ARM) for write operations (create collaboration, manage resources) | ||
|
|
||
| --- | ||
|
|
||
| ## Command Surfaces | ||
|
|
||
| - **Data Plane Commands**: | ||
|
|
||
| | Command Group | Command | Status | | ||
| | --- | --- | --- | | ||
| | Collaborations | `collaborations list` | Completed | | ||
| | Collaborations | `collaborations get` | Pending | | ||
| | Analytics | `analytics get` | Pending | | ||
| | Analytics | `analytics skr-policy` | Pending | | ||
| | OIDC | `oidc issuer-info` | Pending | | ||
| | OIDC | `oidc keys` | Pending | | ||
| | OIDC | `oidc set-issuer-url` | Pending | | ||
| | Invitations | `invitations list` | Pending | | ||
| | Invitations | `invitations accept` | Pending | | ||
| | Datasets | `datasets publish` | Pending | | ||
| | Datasets | `datasets get` | Pending | | ||
| | Datasets | `datasets list` | Pending | | ||
| | Consent | `consent put` | Pending | | ||
| | Queries | `queries publish` | Pending | | ||
| | Queries | `queries get` | Pending | | ||
| | Queries | `queries list` | Pending | | ||
| | Queries | `queries vote` | Pending | | ||
| | Queries | `queries run` | Pending | | ||
| | Queries | `queries runs` | Pending | | ||
| | Runs | `runs get` | Pending | | ||
| | Audit Events | `auditevents list` | Pending | | ||
|
|
||
| - **Management Plane Commands**: | ||
|
|
||
| | Command Group | Command | Status | | ||
| | --- | --- | --- | | ||
| | Collaboration | `collaboration create` | Completed | | ||
| | Collaboration | `collaboration get` | Pending | | ||
| | Collaboration | `collaboration add-collaborator` | Pending | | ||
| | Collaboration | `collaboration enable-workload` | Pending | | ||
| | Collaboration | `collaboration get-readonly-kubeconfig` | Pending | | ||
| --- | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| Azure.Mcp.Tools.ManagedCleanroom/ | ||
| ├── src/ | ||
| │ ├── ManagedCleanroomSetup.cs # DI registration & command tree | ||
| │ ├── Commands/ | ||
| │ │ ├── ManagedCleanroomJsonContext.cs # AOT-safe JSON serialization | ||
| │ │ ├── Collaboration/ | ||
| │ │ │ ├── CollaborationCreateCommand.cs | ||
| │ │ │ └── [Other collaboration commands ] | ||
| │ │ ├── Collaborations/ | ||
| │ │ │ ├── CollaborationsListCommand.cs | ||
| │ │ │ └── [Other collaboration commands ] | ||
| │ │ ├── Analytics/ # Analytics operations | ||
| │ │ ├── Oidc/ # OIDC configuration | ||
| │ │ ├── Invitations/ # Invitation management | ||
| │ │ ├── Datasets/ # Dataset operations | ||
| │ │ ├── Consent/ # Consent documents | ||
| │ │ ├── Queries/ # Query operations | ||
| │ │ ├── Runs/ # Query run tracking | ||
| │ │ └── AuditEvents/ # Audit event listing | ||
| │ ├── Options/ | ||
| │ │ ├── ManagedCleanroomOptionDefinitions.cs | ||
| │ │ ├── Collaboration/ | ||
| │ │ │ └── [Options classes - mixed status] | ||
| │ │ └── [Options for all command groups] | ||
|
Comment on lines
+75
to
+79
|
||
| │ └── Services/ | ||
| │ ├── IManagedCleanroomService.cs | ||
| │ └── ManagedCleanroomService.cs | ||
|
ShreyaSangwa marked this conversation as resolved.
|
||
| └── tests/ | ||
| └── Azure.Mcp.Tools.ManagedCleanroom.Tests/ | ||
| ├── Collaboration/ | ||
| │ ├── CollaborationCreateCommandTests.cs | ||
| │ └── [Other tests - ⏳] | ||
| ├── Collaborations/ | ||
| │ ├── CollaborationsListCommandTests.cs | ||
| │ └── [Other tests - ⏳] | ||
| └── [Tests for remaining command groups - ⏳] | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| - **Completed**: `collaborations list`, `collaboration create` | ||
| - **Pending**: 25 additional commands across 9 command groups | ||
| - Commands span both data plane and control plane operations | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Runtime.CompilerServices; | ||
|
|
||
| [assembly: InternalsVisibleTo("Azure.Mcp.Tools.ManagedCleanroom.Tests")] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <IsAotCompatible>true</IsAotCompatible> | ||
| <AzureSupportedClouds>AzureCloud</AzureSupportedClouds> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\..\core\Azure.Mcp.Core\src\Azure.Mcp.Core.csproj" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <!-- Cleanroom SDKs are temporarily vendored as NuGet packages until they are published. --> | ||
| <PackageReference Include="Azure.Cleanroom.Analytics.Frontend.Client" /> | ||
| <PackageReference Include="Azure.Core" VersionOverride="1.57.0" /> | ||
| <PackageReference Include="Azure.Identity" /> | ||
| <PackageReference Include="Azure.ResourceManager.CleanRoom" /> | ||
| <PackageReference Include="Azure.ResourceManager.Resources" /> | ||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" /> | ||
| <PackageReference Include="ModelContextProtocol" /> | ||
| <PackageReference Include="System.CommandLine" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Net; | ||
| using Azure.Mcp.Core.Commands.Subscription; | ||
| using Azure.Mcp.Core.Services.Azure.Subscription; | ||
| using Azure.Mcp.Tools.ManagedCleanroom.Options.Collaboration; | ||
| using Azure.Mcp.Tools.ManagedCleanroom.Services; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Mcp.Core.Commands; | ||
| using Microsoft.Mcp.Core.Models.Command; | ||
|
|
||
| namespace Azure.Mcp.Tools.ManagedCleanroom.Commands.Collaboration; | ||
|
|
||
| [CommandMetadata( | ||
| Id = "e247b9e0-2d87-43a7-8e5d-57eea22237a3", | ||
| Name = "create", | ||
| Title = "Create Cleanroom Collaboration", | ||
| Description = """ | ||
| Creates an Azure Cleanroom collaboration ARM resource in the specified resource group and location. | ||
| Polls provisioningState every 30 seconds until the resource reaches a terminal state (expected ~25 minutes). | ||
|
ShreyaSangwa marked this conversation as resolved.
Outdated
|
||
| Returns the final ARM resource properties and a summary message with the outcome and elapsed time. | ||
| Required options: | ||
| - --name: unique collaboration name within the resource group | ||
| - --location: Azure region for the ARM resource (e.g., 'eastus') | ||
| - --resource-group: resource group to create the collaboration in | ||
| - --subscription: Azure subscription | ||
| """, | ||
| Destructive = false, | ||
| Idempotent = true, | ||
| OpenWorld = false, | ||
| ReadOnly = false, | ||
| Secret = false, | ||
| LocalRequired = false)] | ||
| public sealed class CollaborationCreateCommand( | ||
| ILogger<CollaborationCreateCommand> logger, | ||
| IManagedCleanroomService service, | ||
| ISubscriptionResolver subscriptionResolver) | ||
| : SubscriptionCommand<CollaborationCreateOptions, CollaborationCreateCommand.CollaborationCreateCommandResult>(subscriptionResolver) | ||
| { | ||
| private readonly ILogger<CollaborationCreateCommand> _logger = logger; | ||
| private readonly IManagedCleanroomService _service = service; | ||
|
|
||
| public override async Task<CommandResponse> ExecuteAsync( | ||
| CommandContext context, CollaborationCreateOptions options, CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| var result = await _service.CreateCollaborationArmResourceAsync( | ||
| options.Name, | ||
| options.ResourceGroup, | ||
| options.Subscription!, | ||
| options.Location, | ||
| options.ResourceLocation, | ||
| options.Collaborators, | ||
| options.Tenant, | ||
| options.RetryPolicy, | ||
| cancellationToken).ConfigureAwait(false); | ||
|
|
||
| context.Response.Message = result.Message; | ||
| context.Response.Results = ResponseResult.Create( | ||
| result.Properties, | ||
| ManagedCleanroomJsonContext.Default.JsonElement); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, | ||
| "Error creating cleanroom collaboration. Name: {Name}, ResourceGroup: {ResourceGroup}, Subscription: {Subscription}", | ||
| options.Name, options.ResourceGroup, options.Subscription); | ||
| HandleException(context, ex); | ||
| } | ||
|
|
||
| return context.Response; | ||
| } | ||
|
|
||
| protected override string GetErrorMessage(Exception ex) => ex switch | ||
| { | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Conflict => | ||
| "A collaboration with this name already exists in the resource group.", | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Forbidden => | ||
| $"Authorization failed creating the collaboration. Details: {reqEx.Message}", | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.NotFound => | ||
| "Resource group not found. Verify the resource group exists and you have access.", | ||
| RequestFailedException reqEx => reqEx.Message, | ||
| _ => base.GetErrorMessage(ex) | ||
| }; | ||
|
|
||
| protected override HttpStatusCode GetStatusCode(Exception ex) => ex switch | ||
| { | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Conflict => | ||
| HttpStatusCode.Conflict, | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Forbidden => | ||
| HttpStatusCode.Forbidden, | ||
| RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.NotFound => | ||
| HttpStatusCode.NotFound, | ||
| RequestFailedException reqEx => (HttpStatusCode)reqEx.Status, | ||
| _ => base.GetStatusCode(ex) | ||
| }; | ||
|
|
||
| public record CollaborationCreateCommandResult; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Azure.Mcp.Tools.ManagedCleanroom.Options.Collaborations; | ||
| using Azure.Mcp.Tools.ManagedCleanroom.Services; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Mcp.Core.Commands; | ||
| using Microsoft.Mcp.Core.Models.Command; | ||
| using Microsoft.Mcp.Core.Models.Option; | ||
|
|
||
| namespace Azure.Mcp.Tools.ManagedCleanroom.Commands.Collaborations; | ||
|
|
||
| [CommandMetadata( | ||
| Id = "0d6a0a0e-7a3a-4a7c-8e3f-2c0d2cfb91a1", | ||
| Name = "list", | ||
| Title = "List Cleanroom Collaborations", | ||
| Description = "Lists Azure Cleanroom collaborations the calling user participates in via the Cleanroom Analytics Frontend service. Returns the full collaboration details from the service.", | ||
| Destructive = false, | ||
| Idempotent = true, | ||
| OpenWorld = false, | ||
| ReadOnly = true, | ||
| Secret = false, | ||
| LocalRequired = false)] | ||
| public sealed class CollaborationsListCommand(ILogger<CollaborationsListCommand> logger, IManagedCleanroomService service) | ||
| : AuthenticatedCommand<CollaborationsListOptions, CollaborationsListCommand.CollaborationsListCommandResult> | ||
| { | ||
| private readonly ILogger<CollaborationsListCommand> _logger = logger; | ||
| private readonly IManagedCleanroomService _service = service; | ||
|
|
||
| public override async Task<CommandResponse> ExecuteAsync( | ||
| CommandContext context, CollaborationsListOptions options, CancellationToken cancellationToken) | ||
| { | ||
| try | ||
| { | ||
| var result = await _service.ListCollaborationsAsync( | ||
| options.Endpoint, | ||
| options.ActiveOnly, | ||
| options.AllowUntrustedCert, | ||
|
ShreyaSangwa marked this conversation as resolved.
Outdated
|
||
| options.TokenScope, | ||
| options.Tenant, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this actually operate at a tenant level? Or is scoping handled by |
||
| cancellationToken).ConfigureAwait(false); | ||
|
Comment on lines
+35
to
+41
|
||
|
|
||
| context.Response.Results = ResponseResult.Create( | ||
| result, | ||
| ManagedCleanroomJsonContext.Default.JsonElement); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, | ||
| "Error listing cleanroom collaborations. Endpoint: {Endpoint} ActiveOnly: {ActiveOnly}", | ||
| options.Endpoint, options.ActiveOnly); | ||
| HandleException(context, ex); | ||
| } | ||
|
|
||
| return context.Response; | ||
| } | ||
|
|
||
| public record CollaborationsListCommandResult; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||
| // Copyright (c) Microsoft Corporation. | ||||||
| // Licensed under the MIT License. | ||||||
|
|
||||||
| using System.Text.Json; | ||||||
| using System.Text.Json.Serialization; | ||||||
|
|
||||||
| namespace Azure.Mcp.Tools.ManagedCleanroom.Commands; | ||||||
|
|
||||||
| // Only JsonElement is registered here because the current Managed Cleanroom | ||||||
| // commands pass through raw JSON payloads from HTTP/ARM responses directly to | ||||||
| // ResponseResult.Create without wrapping them in typed result models. | ||||||
| [JsonSerializable(typeof(JsonElement))] | ||||||
| [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Unless indentation is really required for consuming the response (ex, it's specially formatted content for a human to read) drop indentation. It's just additional wasted tokens due to whitespacing. An LLM will understand the response either way as JSON doesn't care about whitespacing between properties. |
||||||
| internal partial class ManagedCleanroomJsonContext : JsonSerializerContext; | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.