Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@
# ServiceLabel: %tools-ManagedLustre
# ServiceOwners: @rebecca-makar @wolfgang-desalvador

# PRLabel: %tools-ManagedCleanroom
/tools/Azure.Mcp.Tools.ManagedCleanroom/ @ashank @yavohra @vaidmishra @microsoft/azure-mcp

# ServiceLabel: %tools-ManagedCleanroom
# ServiceOwners: @ashank @yavohra @vaidmishra @microsoft/azure-mcp

# PRLabel: %tools-Marketplace
/tools/Azure.Mcp.Tools.Marketplace/ @meirloichter @shaharsandak @obit91 @microsoft/azure-mcp

Expand Down
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<ItemGroup>
<PackageVersion Include="Apache.Arrow" Version="22.1.0" />
<PackageVersion Include="Azure.Containers.ContainerRegistry" Version="1.2.0" />
<!-- Cleanroom SDKs are temporarily added as vendored NuGet packages until they are published. -->
<PackageVersion Include="Azure.Cleanroom.Analytics.Frontend.Client" Version="1.0.0-beta.1" />
Comment thread
ShreyaSangwa marked this conversation as resolved.
<PackageVersion Include="Azure.Communication.Sms" Version="1.0.2" />
<PackageVersion Include="Azure.Communication.Email" Version="1.0.2" />
<PackageVersion Include="Azure.AI.Projects" Version="1.0.0-beta.9" />
Expand All @@ -31,6 +33,8 @@
<PackageVersion Include="Azure.ResourceManager.DesktopVirtualization" Version="1.3.2" />
<PackageVersion Include="Azure.ResourceManager.DeviceRegistry" Version="1.1.0-beta.2" />
<PackageVersion Include="Azure.ResourceManager.ContainerService" Version="1.2.5" />
<!-- Cleanroom SDKs are temporarily added as vendored NuGet packages until they are published. -->
<PackageVersion Include="Azure.ResourceManager.CleanRoom" Version="1.0.0-alpha.20260603.1" />
<PackageVersion Include="Azure.ResourceManager.EventGrid" Version="1.2.0-beta.2" />
<PackageVersion Include="Azure.ResourceManager.EventHubs" Version="1.2.1" />
<PackageVersion Include="Azure.ResourceManager.CognitiveServices" Version="1.5.1" />
Expand Down
7 changes: 7 additions & 0 deletions Microsoft.Mcp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@
<Folder Name="/tools/Azure.Mcp.Tools.LoadTesting/tests/">
<Project Path="tools/Azure.Mcp.Tools.LoadTesting/tests/Azure.Mcp.Tools.LoadTesting.Tests/Azure.Mcp.Tools.LoadTesting.Tests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/" />
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/src/">
<Project Path="tools/Azure.Mcp.Tools.ManagedCleanroom/src/Azure.Mcp.Tools.ManagedCleanroom.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/tests/">
<Project Path="tools/Azure.Mcp.Tools.ManagedCleanroom/tests/Azure.Mcp.Tools.ManagedCleanroom.Tests/Azure.Mcp.Tools.ManagedCleanroom.Tests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedLustre/" />
<Folder Name="/tools/Azure.Mcp.Tools.ManagedLustre/src/">
<Project Path="tools/Azure.Mcp.Tools.ManagedLustre/src/Azure.Mcp.Tools.ManagedLustre.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<!-- Do not add any additional feeds. If new packages are needed, they need to come from our azure-sdk-for-net DevOps feed which has an upstream set to nuget.org -->
<!-- See the Central NuGet Feed documentation in CONTRIBUTING.md for more details -->
<add key="azure-sdk-for-net" value="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json" />
<!-- Temporary local feed for vendored Cleanroom SDK NuGet packages until those SDKs are published. -->
<add key="cleanroom-local" value="vendor/cleanroom-nupkgs" />
</packageSources>
<disabledPackageSources>
<clear />
Expand Down
7 changes: 7 additions & 0 deletions servers/Azure.Mcp.Server/Azure.Mcp.Server.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,13 @@
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.LoadTesting/tests/">
<Project Path="../../tools/Azure.Mcp.Tools.LoadTesting/tests/Azure.Mcp.Tools.LoadTesting.Tests/Azure.Mcp.Tools.LoadTesting.Tests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/" />
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/src/">
<Project Path="../../tools/Azure.Mcp.Tools.ManagedCleanroom/src/Azure.Mcp.Tools.ManagedCleanroom.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/tests/">
<Project Path="../../tools/Azure.Mcp.Tools.ManagedCleanroom/tests/Azure.Mcp.Tools.ManagedCleanroom.Tests/Azure.Mcp.Tools.ManagedCleanroom.Tests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedLustre/" />
<Folder Name="/tools/Azure.Mcp.Tools.ManagedLustre/src/">
Expand Down
Comment thread
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`."
1 change: 1 addition & 0 deletions servers/Azure.Mcp.Server/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ private static IAreaSetup[] RegisterAreas()
new Azure.Mcp.Tools.KeyVault.KeyVaultSetup(),
new Azure.Mcp.Tools.Kusto.KustoSetup(),
new Azure.Mcp.Tools.LoadTesting.LoadTestingSetup(),
new Azure.Mcp.Tools.ManagedCleanroom.ManagedCleanroomSetup(),
new Azure.Mcp.Tools.Marketplace.MarketplaceSetup(),
new Azure.Mcp.Tools.Quota.QuotaSetup(),
new Azure.Mcp.Tools.Monitor.MonitorSetup(),
Expand Down
100 changes: 100 additions & 0 deletions tools/Azure.Mcp.Tools.ManagedCleanroom/docs/architecture.md
Comment thread
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
Comment thread
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.
Comment thread
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
Comment thread
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

Empty file.
6 changes: 6 additions & 0 deletions tools/Azure.Mcp.Tools.ManagedCleanroom/src/AssemblyInfo.cs
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).
Comment thread
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,
Comment thread
ShreyaSangwa marked this conversation as resolved.
Outdated
options.TokenScope,
options.Tenant,

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.

Does this actually operate at a tenant level? Or is scoping handled by Endpoint

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)]

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.

Suggested change
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]

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;

Loading
Loading