Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "",
"TagPrefix": "<LiveTestCsProjFileNameWithoutExtension>", // e.g., "Azure.Mcp.Tools.KeyVault.LiveTests"
"TagPrefix": "<LiveTestCsProjFileNameWithoutExtension>", // e.g., "Azure.Mcp.Tools.KeyVault.Tests"
"Tag": ""
}
```
Expand Down
22 changes: 11 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ Microsoft MCP (Model Context Protocol) servers provide AI agents with structured
### Good examples to follow
- Command implementation: `tools/Azure.Mcp.Tools.Storage/src/Commands/Account/StorageAccountGetCommand.cs`
- Service pattern: `tools/Azure.Mcp.Tools.Storage/src/Services/StorageService.cs`
- Unit tests: `tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.UnitTests/Account/StorageAccountGetCommandTests.cs`
- Unit tests: `tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests/Account/StorageAccountGetCommandTests.cs`
- Integration tests: `tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests/StorageCommandTests.cs`
- Live test infrastructure: `tools/Azure.Mcp.Tools.Storage/tests/test-resources.bicep`
- Option definitions: `tools/Azure.Mcp.Tools.Storage/src/Options/StorageOptionDefinitions.cs`

Expand Down Expand Up @@ -226,10 +227,9 @@ Azure.Mcp.Tools.{Service}/
│ ├── Models/ # Data models and DTOs
│ └── {Service}Setup.cs # Service registration and configuration
└── tests/
├── Azure.Mcp.Tools.{Service}.UnitTests/ # Unit tests (no Azure resources)
├── Azure.Mcp.Tools.{Service}.LiveTests/ # Integration tests (requires Azure)
├── test-resources.bicep # Test infrastructure template
└── test-resources-post.ps1 # Post-deployment setup script
├── Azure.Mcp.Tools.{Service}.Tests/ # Unit tests (no Azure resources) and Integration tests (requires Azure)
├── test-resources.bicep # Test infrastructure template
└── test-resources-post.ps1 # Post-deployment setup script
```

### Command Naming Convention
Expand Down Expand Up @@ -270,14 +270,14 @@ dotnet build
# Specific toolset unit tests
./eng/scripts/Test-Code.ps1 -Paths Storage, KeyVault

# Live tests (requires Azure authentication and resources)
./eng/scripts/Test-Code.ps1 -TestType Live -Paths Storage

# Deploy test infrastructure for live tests
./eng/scripts/Deploy-TestResources.ps1 -Paths Storage

# Live tests (requires Azure authentication and resources)
./eng/scripts/Test-Code.ps1 -TestType Live -Paths Storage

# Run tests from specific directory
pushd 'tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.UnitTests'
pushd 'tools/Azure.Mcp.Tools.Storage/tests/Azure.Mcp.Tools.Storage.Tests'
dotnet test --filter "FullyQualifiedName~StorageAccountGetCommandTests"
popd
```
Expand Down Expand Up @@ -514,8 +514,8 @@ tools/Azure.Mcp.Tools.{Service}/
│ ├── Services/{Service}Service.cs # Service implementation
│ └── Commands/{Service}JsonContext.cs # JSON serialization context
└── tests/
├── Azure.Mcp.Tools.{Service}.UnitTests/{Resource}/{Resource}{Operation}CommandTests.cs
├── Azure.Mcp.Tools.{Service}.LiveTests/{Service}CommandTests.cs
├── Azure.Mcp.Tools.{Service}.Tests/{Resource}/{Resource}{Operation}CommandTests.cs # Unit tests (no Azure resources)
├── Azure.Mcp.Tools.{Service}.Tests/{Service}CommandTests.cs # Integration tests (requires Azure)
├── test-resources.bicep # Test infrastructure (Azure services only)
└── test-resources-post.ps1 # Post-deployment script (Azure services only)
```
Expand Down
7 changes: 3 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,20 @@ If you are contributing significant changes, or if the issue is already assigned
- `Fabric.Mcp.Core` - Fabric.Mcp.Core, depends on Azure.Mcp.Core (fabric uses azure)
- `Microsoft.Mcp.Core` - Microsoft.Mcp.Core library
- `servers\`
- `{server}.Mcp.Server - Individual servers (e.g. `Azure.Mcp.Server`, `Fabric.Mcp.Server`)
- `{Server}.Mcp.Server - Individual servers (e.g. `Azure.Mcp.Server`, `Fabric.Mcp.Server`)
- `src` - Source for the server
- `tests` - Any unit or live tests for the server
- `README.md` - Specific readme for this server
- `CHANGELOG.md` - Specific changelog for this server
- `tools/` - Service-specific implementations
- `{server}.Mcp.Tools.{tool-name}/` - Individual server tools (e.g., `Azure.Mcp.Tools.KeyVault`, `Fabric.Mcp.Tools.Admin`)
- `{Server}.Mcp.Tools.{ToolArea}/` - Individual server tools (e.g., `Azure.Mcp.Tools.KeyVault`, `Fabric.Mcp.Tools.Admin`)
- `src` - Service specific code
- `Commands/` - Command implementations
- `Models/` - Service specific models
- `Services/` - Service implementations and interfaces
- `Options/` - Service specific command options
- `tests/` - Service specific tests
- `{server}.Mcp.Tools.{tool-name}.UnitTests/` - Unit tests require no authentication or test resources
- `{server}.Mcp.Tools.{tool-name}.LiveTests/` - Live tests depend on Azure resources and authentication
- `{Server}.Mcp.Tools.{ToolArea}.Tests/` - Unit tests (no Azure resources) and Integraion tests (requires Azure)
- `test-resources.bicep` - Infrastructure templates for testing
- `eng/` - Shared tools, templates, CLI helpers
- `docs/` - Central documentation and onboarding materials
Expand Down
5 changes: 2 additions & 3 deletions Microsoft.Mcp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
<Project Path="core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj" />
</Folder>
<Folder Name="/core/Azure.Mcp.Core/tests/">
<Project Path="core/Azure.Mcp.Core/tests/Azure.Mcp.Core.LiveTests/Azure.Mcp.Core.LiveTests.csproj" />
<Project Path="core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Azure.Mcp.Core.UnitTests.csproj" />
<Project Path="core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Azure.Mcp.Core.Tests.csproj" />
<Project Path="core/Azure.Mcp.Core/tests/Azure.Mcp.Tests/Azure.Mcp.Tests.csproj" />
</Folder>
<Folder Name="/core/Fabric.Mcp.Core/" />
Expand All @@ -18,7 +17,7 @@
<Project Path="core/Microsoft.Mcp.Core/src/Microsoft.Mcp.Core.csproj" />
</Folder>
<Folder Name="/core/Microsoft.Mcp.Core/tests/">
<Project Path="core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.UnitTests/Microsoft.Mcp.Core.UnitTests.csproj" />
<Project Path="core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Microsoft.Mcp.Core.Tests.csproj" />
<Project Path="core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Microsoft.Mcp.Tests.csproj" />
</Folder>
<Folder Name="/core/Microsoft.ModelContextProtocol.HttpServer.Distributed/" />
Expand Down
3 changes: 1 addition & 2 deletions core/Azure.Mcp.Core/src/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Azure.Mcp.Core.UnitTests")]
[assembly: InternalsVisibleTo("Azure.Mcp.Core.LiveTests")]
[assembly: InternalsVisibleTo("Azure.Mcp.Core.Tests")]
[assembly: InternalsVisibleTo("Microsoft.Mcp.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using NSubstitute;
using Xunit;

namespace Azure.Mcp.Core.UnitTests;
namespace Azure.Mcp.Core.Tests;

public class AccessTokenHandlerTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using NSubstitute.ExceptionExtensions;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Group.UnitTests;
namespace Azure.Mcp.Core.Tests.UnitTests.Areas.Group.UnitTests;

public class ResourceGroupListCommandTests : CommandUnitTestsBase<GroupListCommand, IResourceGroupService>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
using NSubstitute.ExceptionExtensions;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Group.UnitTests;
namespace Azure.Mcp.Core.Tests.UnitTests.Areas.Group.UnitTests;

public class ResourceListCommandTests : CommandUnitTestsBase<ResourceListCommand, IResourceGroupService>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.Mcp.Core.Areas.Server.Commands;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server;
namespace Azure.Mcp.Core.Tests.Areas.Server;

public class ArrayOrCollectionElementTypeTests
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
using Microsoft.Mcp.Core.Services.Time;
using NSubstitute;

namespace Azure.Mcp.Core.UnitTests.Areas.Server;
namespace Azure.Mcp.Core.Tests.Areas.Server;

internal class CommandFactoryHelpers
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,15 @@
using NSubstitute;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

/// <summary>
/// Concrete test implementation of BaseDiscoveryStrategy for testing disposal behavior
/// </summary>
public class TestDiscoveryStrategy : BaseDiscoveryStrategy
public class TestDiscoveryStrategy(IEnumerable<IMcpServerProvider> providers, ILogger? logger = null)
: BaseDiscoveryStrategy(logger ?? NullLogger.Instance)
{
private readonly IEnumerable<IMcpServerProvider> _providers;

public TestDiscoveryStrategy(IEnumerable<IMcpServerProvider> providers, ILogger? logger = null) : base(logger ?? NullLogger.Instance)
{
_providers = providers;
}
private readonly IEnumerable<IMcpServerProvider> _providers = providers;

public override Task<IEnumerable<IMcpServerProvider>> DiscoverServersAsync(CancellationToken cancellationToken)
{
Expand All @@ -43,10 +39,7 @@ private static IMcpServerProvider CreateMockServerProvider(string name, string i
return mockProvider;
}

private static BaseDiscoveryStrategy CreateMockStrategy(params IMcpServerProvider[] providers)
{
return new TestDiscoveryStrategy(providers);
}
private static TestDiscoveryStrategy CreateMockStrategy(params IMcpServerProvider[] providers) => new(providers);

[Fact]
public async Task FindServerProvider_WithEmptyDiscovery_ThrowsArgumentException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Microsoft.Mcp.Tests.Client.Helpers;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

public class CommandGroupDiscoveryStrategyTests
{
Expand Down Expand Up @@ -150,8 +150,7 @@ public async Task DiscoverServersAsync_WithReadOnlyFalse_CreatesNonReadOnlyProvi

// Assert
Assert.NotEmpty(result);
Assert.All(result, provider =>
Assert.False(((CommandGroupServerProvider)provider).ReadOnly));
Assert.All(result, provider => Assert.False(((CommandGroupServerProvider)provider).ReadOnly));
}

[Fact]
Expand All @@ -166,8 +165,7 @@ public async Task DiscoverServersAsync_WithReadOnlyTrue_CreatesReadOnlyProviders

// Assert
Assert.NotEmpty(result);
Assert.All(result, provider =>
Assert.True(((CommandGroupServerProvider)provider).ReadOnly));
Assert.All(result, provider => Assert.True(((CommandGroupServerProvider)provider).ReadOnly));
}

[Fact]
Expand All @@ -182,8 +180,7 @@ public async Task DiscoverServersAsync_WithNullReadOnlyOption_DefaultsToFalse()

// Assert
Assert.NotEmpty(result);
Assert.All(result, provider =>
Assert.False(((CommandGroupServerProvider)provider).ReadOnly));
Assert.All(result, provider => Assert.False(((CommandGroupServerProvider)provider).ReadOnly));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
using ModelContextProtocol.Client;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

public class CommandGroupServerProviderTests
{
private readonly ICommandFactory _commandFactory;
public CommandGroupServerProviderTests()
{
_commandFactory = CommandFactoryHelpers.CreateCommandFactory();
}
private readonly ICommandFactory _commandFactory = CommandFactoryHelpers.CreateCommandFactory();

[Fact]
public void CreateMetadata_ReturnsExpectedMetadata()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Areas.Server.Commands.Discovery;
using NSubstitute;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

public class CompositeDiscoveryStrategyTests
{
private static IMcpDiscoveryStrategy CreateMockStrategy(params IMcpServerProvider[] providers)
{
var strategy = Substitute.For<IMcpDiscoveryStrategy>();
strategy.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(Task.FromResult<IEnumerable<IMcpServerProvider>>(providers));
strategy.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(providers);
return strategy;
}

Expand All @@ -29,10 +30,7 @@ private static IMcpServerProvider CreateMockProvider(string id, string? name = n
}

private static CompositeDiscoveryStrategy CreateCompositeStrategy(IEnumerable<IMcpDiscoveryStrategy> strategies)
{
var logger = Substitute.For<Microsoft.Extensions.Logging.ILogger<CompositeDiscoveryStrategy>>();
return new CompositeDiscoveryStrategy(strategies, logger);
}
=> new(strategies, Substitute.For<ILogger<CompositeDiscoveryStrategy>>());

[Fact]
public void Constructor_WithValidStrategies_InitializesCorrectly()
Expand All @@ -54,30 +52,17 @@ public void Constructor_WithValidStrategies_InitializesCorrectly()
public void Constructor_WithNullStrategies_ThrowsArgumentNullException()
{
// Act & Assert
var logger = Substitute.For<Microsoft.Extensions.Logging.ILogger<CompositeDiscoveryStrategy>>();
var exception = Assert.Throws<ArgumentNullException>(() =>
new CompositeDiscoveryStrategy(null!, logger));
var logger = Substitute.For<ILogger<CompositeDiscoveryStrategy>>();
var exception = Assert.Throws<ArgumentNullException>(() => new CompositeDiscoveryStrategy(null!, logger));
Assert.Equal("strategies", exception.ParamName);
}

[Fact]
public void Constructor_WithEmptyStrategies_ThrowsArgumentException()
{
// Act & Assert
var logger = Substitute.For<Microsoft.Extensions.Logging.ILogger<CompositeDiscoveryStrategy>>();
var exception = Assert.Throws<ArgumentException>(() =>
new CompositeDiscoveryStrategy([], logger));
Assert.Equal("strategies", exception.ParamName);
Assert.Contains("At least one discovery strategy must be provided", exception.Message);
}

[Fact]
public void DiscoverServersAsync_WithEmptyStrategies_ThrowsArgumentException()
{
// Act & Assert
var logger = Substitute.For<Microsoft.Extensions.Logging.ILogger<CompositeDiscoveryStrategy>>();
var exception = Assert.Throws<ArgumentException>(() =>
new CompositeDiscoveryStrategy([], logger));
var logger = Substitute.For<ILogger<CompositeDiscoveryStrategy>>();
var exception = Assert.Throws<ArgumentException>(() => new CompositeDiscoveryStrategy([], logger));
Assert.Equal("strategies", exception.ParamName);
Assert.Contains("At least one discovery strategy must be provided", exception.Message);
}
Expand Down Expand Up @@ -281,8 +266,8 @@ public async Task ShouldAggregateResults()
var mockStrategy2 = Substitute.For<IMcpDiscoveryStrategy>();
var provider1 = Substitute.For<IMcpServerProvider>();
var provider2 = Substitute.For<IMcpServerProvider>();
mockStrategy1.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(Task.FromResult<IEnumerable<IMcpServerProvider>>([provider1]));
mockStrategy2.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(Task.FromResult<IEnumerable<IMcpServerProvider>>([provider2]));
mockStrategy1.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns([provider1]);
mockStrategy2.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns([provider2]);
var composite = CreateCompositeStrategy([mockStrategy1, mockStrategy2]);
var result = await composite.DiscoverServersAsync(TestContext.Current.CancellationToken);
Assert.Contains(provider1, result);
Expand All @@ -298,8 +283,8 @@ public async Task ShouldAggregateResults_ReturnsAllProviders()
var provider2 = Substitute.For<IMcpServerProvider>();
provider1.CreateMetadata().Returns(new McpServerMetadata { Id = "one", Name = "one", Description = "desc1" });
provider2.CreateMetadata().Returns(new McpServerMetadata { Id = "two", Name = "two", Description = "desc2" });
mockStrategy1.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(Task.FromResult<IEnumerable<IMcpServerProvider>>([provider1]));
mockStrategy2.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns(Task.FromResult<IEnumerable<IMcpServerProvider>>([provider2]));
mockStrategy1.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns([provider1]);
mockStrategy2.DiscoverServersAsync(TestContext.Current.CancellationToken).Returns([provider2]);
var composite = CreateCompositeStrategy([mockStrategy1, mockStrategy2]);
var result = (await composite.DiscoverServersAsync(TestContext.Current.CancellationToken)).ToList();
Assert.Equal(2, result.Count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// Licensed under the MIT License.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Areas.Server.Commands.Discovery;
using Microsoft.Mcp.Core.Areas.Server.Options;
using Microsoft.Mcp.Core.Commands;
using Microsoft.Mcp.Core.Configuration;
using NSubstitute;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

public class ConsolidatedToolDiscoveryStrategyTests
{
Expand All @@ -29,9 +30,9 @@ private static ConsolidatedToolDiscoveryStrategy CreateStrategy(
RootCommandGroupName = "azmcp"
});

var logger = Substitute.For<Microsoft.Extensions.Logging.ILogger<ConsolidatedToolDiscoveryStrategy>>();
var providerLogger = Substitute.For<Microsoft.Extensions.Logging.ILogger<ResourceConsolidatedToolDefinitionProvider>>();
var serverAssembly = typeof(Azure.Mcp.Server.Program).Assembly;
var logger = Substitute.For<ILogger<ConsolidatedToolDiscoveryStrategy>>();
var providerLogger = Substitute.For<ILogger<ResourceConsolidatedToolDefinitionProvider>>();
var serverAssembly = typeof(Mcp.Server.Program).Assembly;

ResourceConsolidatedToolDefinitionProvider definitionProvider = new(providerLogger, serverAssembly, "consolidated-tools.json");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.Mcp.Core.Areas.Server.Options;
using Xunit;

namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery;
namespace Azure.Mcp.Core.Tests.Areas.Server.Commands.Discovery;

public class RegistryDiscoveryStrategyTests
{
Expand Down
Loading
Loading