Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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/ @microsoft/azure-mcp @ashank @yavohra @vaidmishra
Comment thread
ShreyaSangwa marked this conversation as resolved.
Outdated

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

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

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
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 @@ -158,6 +158,13 @@
<Folder Name="/tools/Azure.Mcp.Tools.ConfidentialLedger/tests/">
<Project Path="../../tools/Azure.Mcp.Tools.ConfidentialLedger/tests/Azure.Mcp.Tools.ConfidentialLedger.Tests/Azure.Mcp.Tools.ConfidentialLedger.Tests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.ManagedCleanroom/" />
Comment thread
ShreyaSangwa marked this conversation as resolved.
Outdated
<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.ContainerApps/" />
<Folder Name="/tools/Azure.Mcp.Tools.ContainerApps/src/">
<Project Path="../../tools/Azure.Mcp.Tools.ContainerApps/src/Azure.Mcp.Tools.ContainerApps.csproj" />
Expand Down
3 changes: 3 additions & 0 deletions servers/Azure.Mcp.Server/changelog-entries/1780647747781.yaml
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
94 changes: 94 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,94 @@
# 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

| Command Group | Command | Plane | Status |
Comment thread
ShreyaSangwa marked this conversation as resolved.
Outdated
| --- | --- | --- | --- |
| Collaborations | `collaborations list` | Data Plane | Completed |
| Collaborations | `collaborations get` | Data Plane | Pending |
| Analytics | `analytics get` | Data Plane | Pending |
| Analytics | `analytics skr-policy` | Data Plane | Pending |
| OIDC | `oidc issuer-info` | Data Plane | Pending |
| OIDC | `oidc keys` | Data Plane | Pending |
| OIDC | `oidc set-issuer-url` | Data Plane | Pending |
| Collaboration | `collaboration create` | Control Plane | Completed |
| Collaboration | `collaboration get` | Control Plane | Pending |
| Collaboration | `collaboration add-collaborator` | Control Plane | Pending |
| Collaboration | `collaboration enable-workload` | Control Plane | Pending |
| Collaboration | `collaboration get-readonly-kubeconfig` | Control Plane | Pending |
| Invitations | `invitations list` | Data Plane | Pending |
| Invitations | `invitations accept` | Data Plane | Pending |
| Datasets | `datasets publish` | Data Plane | Pending |
| Datasets | `datasets get` | Data Plane | Pending |
| Datasets | `datasets list` | Data Plane | Pending |
| Consent | `consent put` | Data Plane | Pending |
| Queries | `queries publish` | Data Plane | Pending |
| Queries | `queries get` | Data Plane | Pending |
| Queries | `queries list` | Data Plane | Pending |
| Queries | `queries vote` | Data Plane | Pending |
| Queries | `queries run` | Data Plane | Pending |
| Queries | `queries runs` | Data Plane | Pending |
| Runs | `runs get` | Data Plane | Pending |
| Audit Events | `auditevents list` | Data Plane | 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

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,17 @@
<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>
<PackageReference Include="Azure.Core" />
<PackageReference Include="Azure.Identity" />
<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