diff --git a/Directory.Packages.props b/Directory.Packages.props index 937a9a590d..499aa31b41 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -60,6 +60,7 @@ + @@ -88,14 +89,15 @@ - - - - - - - - + + + + + + + + + diff --git a/README.md b/README.md index 0923237cb1..191bc01762 100644 --- a/README.md +++ b/README.md @@ -181,11 +181,11 @@ This repository contains core libraries, test frameworks, engineering systems, p - **INSTALL**: [![Install Microsoft Sentinel Data Exploration MCP in VS Code](https://img.shields.io/badge/VS_Code-0098FF?style=flat-square&logo=visualstudiocode&logoColor=ffffff)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22microsoft-sentinel-data-exploration%22%2C%22url%22%3A%22https%3A%2F%2Fsentinel.microsoft.com%2Fmcp%2Fdata-exploration%22%7D) [![Install Microsoft Sentinel Data Exploration MCP in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=ffffff)](https://vscode.dev/redirect?url=vscode-insiders:mcp/install?%7B%22name%22%3A%22microsoft-sentinel-data-exploration%22%2C%22url%22%3A%22https%3A%2F%2Fsentinel.microsoft.com%2Fmcp%2Fdata-exploration%22%7D) ### 🛢️ Microsoft SQL -- **REPOSITORY**: [MSSQL MCP Server](https://aka.ms/MssqlMcp) +- **REPOSITORY**: [MSSQL MCP Server](https://aka.ms/sql/mcp) - **DESCRIPTION**: Chat with your business data the new agentic way using natural language and AI. Connect to any SQL database—from ground (on-premises) to Azure cloud to Microsoft Fabric via a simple connection string. Discover and define table schemas, manage tables, and perform CRUD operations through conversational prompts. - **CATEGORY**: `DEVELOPER TOOLS` - **TYPE**: `Local` -- **INSTALL**: [MSSQL MCP Server](https://aka.ms/MssqlMcp) +- **INSTALL**: [MSSQL MCP Server](https://aka.ms/sql/mcp) ### 💬 Microsoft Teams - **REPOSITORY**: [bap-microsoft/MCP-Platform](https://github.com/bap-microsoft/MCP-Platform/tree/main/src/Services/WebApi/MCPServers/FirstParty/CodeBased/mcp_TeamsServer) diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.LiveTests/Azure.Mcp.Core.LiveTests.csproj b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.LiveTests/Azure.Mcp.Core.LiveTests.csproj index 45eb7dff93..fa38723dda 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.LiveTests/Azure.Mcp.Core.LiveTests.csproj +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.LiveTests/Azure.Mcp.Core.LiveTests.csproj @@ -4,6 +4,7 @@ Exe + diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ServiceInfoCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ServiceInfoCommandTests.cs new file mode 100644 index 0000000000..d8c728ee54 --- /dev/null +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.Tests/Areas/Server/Commands/ServiceInfoCommandTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Mcp.Core.Areas.Server.Commands; +using Microsoft.Mcp.Core.Configuration; +using Microsoft.Mcp.Tests.Client; +using Xunit; + +namespace Azure.Mcp.Core.Tests.Areas.Server.Commands; + +public class ServiceInfoCommandTests : CommandUnitTestsBase +{ + private readonly McpServerConfiguration _mcpServerConfiguration; + + public ServiceInfoCommandTests() + { + _mcpServerConfiguration = new McpServerConfiguration + { + Name = "Test-Name?", + ShortName = "test", + Version = "Test-Version?", + DisplayName = "Test Display", + Description = "Test Description", + RootCommandGroupName = "azmcp" + }; + Services.AddSingleton(Microsoft.Extensions.Options.Options.Create(_mcpServerConfiguration)); + } + + [Fact] + public async Task ExecuteAsync_ReturnsCorrectProperties() + { + var response = await ExecuteCommandAsync([]); + + // Assert + var result = ValidateAndDeserializeResponse(response, ServiceInfoJsonContext.Default.ServiceInfoCommandResult); + + Assert.Equal(_mcpServerConfiguration.Name, result.Name); + Assert.Equal(_mcpServerConfiguration.Version, result.Version); + } +} diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs index 344c181d93..d57e65aa1e 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs @@ -111,8 +111,10 @@ public static ICommandFactory CreateCommandFactory(IServiceProvider? serviceProv var configurationOptions = Microsoft.Extensions.Options.Options.Create(new McpServerConfiguration { Name = "Test Server", + ShortName = "test", Version = "Test Version", DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }); var telemetryService = services.GetService() ?? new NoOpTelemetryService(); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs index feaea83980..febfe522e4 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs @@ -24,8 +24,10 @@ private static ConsolidatedToolDiscoveryStrategy CreateStrategy( var configurationOptions = Microsoft.Extensions.Options.Options.Create(new McpServerConfiguration { Name = "Test Server", + ShortName = "test", Version = "Test Version", DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsTests.cs index 4978853c95..f725e14cc7 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceCollectionExtensionsTests.cs @@ -10,6 +10,7 @@ using Microsoft.Mcp.Core.Areas.Server.Commands.ServerInstructions; using Microsoft.Mcp.Core.Areas.Server.Commands.ToolLoading; using Microsoft.Mcp.Core.Areas.Server.Options; +using Microsoft.Mcp.Core.Configuration; using Microsoft.Mcp.Core.Services.Azure.Authentication; using ModelContextProtocol.Server; using NSubstitute; @@ -25,6 +26,19 @@ private IServiceCollection SetupBaseServices() services.AddSingleton(sp => CommandFactoryHelpers.CreateCommandFactory(sp)); services.AddSingleIdentityTokenCredentialProvider(); services.AddRegistryRoot(typeof(Azure.Mcp.Server.Program).Assembly, "Azure.Mcp.Server.Resources.registry.json"); + + var serverConfiguration = new McpServerConfiguration + { + Name = "Azure.Mcp.Server", + ShortName = "azure", + DisplayName = "Azure MCP Server", + Version = "1.0.0", + RootCommandGroupName = "azmcp", + Description = "Test description" + }; + services.AddSingleton(serverConfiguration); + services.AddSingleton(Microsoft.Extensions.Options.Options.Create(serverConfiguration)); + return services; } diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceInfoCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceInfoCommandTests.cs index 5803f4fbe2..3f337185db 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceInfoCommandTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ServiceInfoCommandTests.cs @@ -32,8 +32,10 @@ public ServiceInfoCommandTests() _mcpServerConfiguration = new McpServerConfiguration { Name = "Test-Name?", + ShortName = "test", Version = "Test-Version?", DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; _command = new(Microsoft.Extensions.Options.Options.Create(_mcpServerConfiguration), _logger); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/PluginTelemetryCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/PluginTelemetryCommandTests.cs index cf1c0c2027..c5a082e1e1 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/PluginTelemetryCommandTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/PluginTelemetryCommandTests.cs @@ -46,7 +46,9 @@ public PluginTelemetryCommandTests() { RootCommandGroupName = "azmcp", Name = "Azure.Mcp.Server.Test", + ShortName = "azure", DisplayName = "Azure MCP Server (Test)", + Description = "Azure MCP Server (Test) description", Version = "1.0.0-test" })); services.AddSingleton(Substitute.For()); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/SingleProxyToolLoaderTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/SingleProxyToolLoaderTests.cs index c5d1e97ac9..d8ace44112 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/SingleProxyToolLoaderTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/SingleProxyToolLoaderTests.cs @@ -9,6 +9,7 @@ using Microsoft.Mcp.Core.Areas.Server.Commands.Discovery; using Microsoft.Mcp.Core.Areas.Server.Commands.ToolLoading; using Microsoft.Mcp.Core.Areas.Server.Options; +using Microsoft.Mcp.Core.Configuration; using Microsoft.Mcp.Core.Helpers; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; @@ -19,6 +20,19 @@ namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.ToolLoading; public class SingleProxyToolLoaderTests { + private static Microsoft.Extensions.Options.IOptions CreateServerConfigurationOptions() + { + return Microsoft.Extensions.Options.Options.Create(new McpServerConfiguration + { + Name = "Azure.Mcp.Server", + ShortName = "azure", + DisplayName = "Azure MCP Server", + Version = "1.0.0", + RootCommandGroupName = "azmcp", + Description = "This server/tool provides real-time, programmatic access to all Azure products, services, and resources." + }); + } + private static RegistryDiscoveryStrategy CreateStrategy(ServiceStartOptions options, ILogger logger) { var serviceOptions = Microsoft.Extensions.Options.Options.Create(options ?? new ServiceStartOptions()); @@ -49,13 +63,13 @@ private static (SingleProxyToolLoader toolLoader, IMcpDiscoveryStrategy discover commandGroupDiscoveryStrategy, registryDiscoveryStrategy ], compositeLogger); - var toolLoader = new SingleProxyToolLoader(compositeDiscoveryStrategy, logger, Microsoft.Extensions.Options.Options.Create(toolLoaderOptions ?? new ToolLoaderOptions())); + var toolLoader = new SingleProxyToolLoader(compositeDiscoveryStrategy, logger, Microsoft.Extensions.Options.Options.Create(toolLoaderOptions ?? new ToolLoaderOptions()), CreateServerConfigurationOptions()); return (toolLoader, compositeDiscoveryStrategy); } else { var mockDiscoveryStrategy = Substitute.For(); - var toolLoader = new SingleProxyToolLoader(mockDiscoveryStrategy, logger, Microsoft.Extensions.Options.Options.Create(toolLoaderOptions ?? new ToolLoaderOptions())); + var toolLoader = new SingleProxyToolLoader(mockDiscoveryStrategy, logger, Microsoft.Extensions.Options.Options.Create(toolLoaderOptions ?? new ToolLoaderOptions()), CreateServerConfigurationOptions()); return (toolLoader, mockDiscoveryStrategy); } } @@ -285,7 +299,7 @@ public async Task GetChildToolList_WithReadOnlyOption_ReturnsOnlyReadOnlyTools() var toolLoaderOptions = Microsoft.Extensions.Options.Options.Create(new ToolLoaderOptions() { ReadOnly = true }); var logger = Substitute.For>(); - var toolLoader = new SingleProxyToolLoader(discoveryStrategy, logger, toolLoaderOptions); + var toolLoader = new SingleProxyToolLoader(discoveryStrategy, logger, toolLoaderOptions, CreateServerConfigurationOptions()); var request = CreateCallToolRequest("storage"); // Act @@ -339,7 +353,7 @@ public async Task GetChildToolList_WithIsHttpOption_DoesNotReturnLocalRequiredTo var toolLoaderOptions = Microsoft.Extensions.Options.Options.Create(new ToolLoaderOptions() { IsHttpMode = true }); var logger = Substitute.For>(); - var toolLoader = new SingleProxyToolLoader(discoveryStrategy, logger, toolLoaderOptions); + var toolLoader = new SingleProxyToolLoader(discoveryStrategy, logger, toolLoaderOptions, CreateServerConfigurationOptions()); var request = CreateCallToolRequest("storage"); // Act @@ -394,11 +408,13 @@ public void SingleProxyToolLoader_Constructor_ThrowsOnNullArguments() var logger = Substitute.For>(); var discoveryStrategy = Substitute.For(); var toolLoaderOptions = Microsoft.Extensions.Options.Options.Create(new ToolLoaderOptions()); + var serverConfigurationOptions = CreateServerConfigurationOptions(); // Act & Assert - Assert.Throws(() => new SingleProxyToolLoader(null!, logger, toolLoaderOptions)); - Assert.Throws(() => new SingleProxyToolLoader(discoveryStrategy, null!, toolLoaderOptions)); - Assert.Throws(() => new SingleProxyToolLoader(discoveryStrategy, logger, null!)); + Assert.Throws(() => new SingleProxyToolLoader(null!, logger, toolLoaderOptions, serverConfigurationOptions)); + Assert.Throws(() => new SingleProxyToolLoader(discoveryStrategy, null!, toolLoaderOptions, serverConfigurationOptions)); + Assert.Throws(() => new SingleProxyToolLoader(discoveryStrategy, logger, null!, serverConfigurationOptions)); + Assert.Throws(() => new SingleProxyToolLoader(discoveryStrategy, logger, toolLoaderOptions, null!)); } #region Execution-Time Mode Enforcement Tests @@ -413,7 +429,7 @@ private static SingleProxyToolLoader CreateToolLoaderWithMockClient( var logger = Substitute.For>(); var options = Microsoft.Extensions.Options.Options.Create(toolLoaderOptions); - return new SingleProxyToolLoader(discoveryStrategy, logger, options); + return new SingleProxyToolLoader(discoveryStrategy, logger, options, CreateServerConfigurationOptions()); } private static ModelContextProtocol.Server.RequestContext CreateCallToolRequestWithToolAndCommand( diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs index 70d663a147..6844c9e7fa 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs @@ -399,8 +399,10 @@ public async Task ExecuteAsync_WithEmptyCommandFactory_ReturnsEmptyResults() var configurationOptions = Microsoft.Extensions.Options.Options.Create(new McpServerConfiguration { Name = "Test Server", + ShortName = "test", Version = "Test Version", DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs index 6257aa26db..ade6c8ea89 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs @@ -35,8 +35,10 @@ public CommandFactoryTests() _serverConfiguration = new McpServerConfiguration { Name = "Test Server", + ShortName = "test", Version = "Test Version", DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index 16b03d8c6d..960c4e39d9 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Reflection; +using System.Text.RegularExpressions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -26,8 +27,11 @@ namespace Microsoft.Mcp.Core.Areas.Server.Commands; /// /// Extension methods for configuring Azure MCP server services. /// -public static class ServiceCollectionExtensions +public static partial class ServiceCollectionExtensions { + [GeneratedRegex("^[A-Za-z0-9_-]+$")] + private static partial Regex ShortNamePattern(); + /// /// Adds the Azure MCP server services to the specified . /// @@ -266,13 +270,31 @@ public static void InitializeConfigurationAndOptions(this IServiceCollection ser .Configure>((options, rootConfiguration, serviceStartOptions) => { // Manually bind configuration values to avoid reflection-based binding for AOT compatibility - options.RootCommandGroupName = rootConfiguration[nameof(McpServerConfiguration.RootCommandGroupName)] + var mcpConfiguration = rootConfiguration.GetRequiredSection("MicrosoftMcp"); + options.RootCommandGroupName = mcpConfiguration[nameof(McpServerConfiguration.RootCommandGroupName)] ?? throw new InvalidOperationException($"Configuration value '{nameof(McpServerConfiguration.RootCommandGroupName)}' is required."); - options.Name = rootConfiguration[nameof(McpServerConfiguration.Name)] + options.Name = mcpConfiguration[nameof(McpServerConfiguration.Name)] ?? throw new InvalidOperationException($"Configuration value '{nameof(McpServerConfiguration.Name)}' is required."); - options.DisplayName = rootConfiguration[nameof(McpServerConfiguration.DisplayName)] + options.DisplayName = mcpConfiguration[nameof(McpServerConfiguration.DisplayName)] ?? throw new InvalidOperationException($"Configuration value '{nameof(McpServerConfiguration.DisplayName)}' is required."); + options.ShortName = mcpConfiguration[nameof(McpServerConfiguration.ShortName)] + ?? throw new InvalidOperationException($"Configuration value '{nameof(McpServerConfiguration.ShortName)}' is required."); + options.ShortName = options.ShortName.Trim(); + if (!ShortNamePattern().IsMatch(options.ShortName)) + { + throw new InvalidOperationException( + $"Configuration value '{nameof(McpServerConfiguration.ShortName)}' must contain only letters, digits, '_', or '-'."); + } + + options.Description = mcpConfiguration[nameof(McpServerConfiguration.Description)] + ?? throw new InvalidOperationException($"Configuration value '{nameof(McpServerConfiguration.Description)}' is required."); + if (string.IsNullOrWhiteSpace(options.Description)) + { + throw new InvalidOperationException( + $"Configuration value '{nameof(McpServerConfiguration.Description)}' must not be empty or whitespace."); + } + // Assembly.GetEntryAssembly is used to retrieve the version of the server application as that is // the assembly that will run the tool calls. var entryAssembly = Assembly.GetEntryAssembly(); diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/SingleProxyToolLoader.cs b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/SingleProxyToolLoader.cs index a7c1830e1c..a308e97925 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/SingleProxyToolLoader.cs +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/SingleProxyToolLoader.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Options; using Microsoft.Mcp.Core.Areas.Server.Commands.Discovery; using Microsoft.Mcp.Core.Commands; +using Microsoft.Mcp.Core.Configuration; using ModelContextProtocol; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; @@ -16,13 +17,19 @@ namespace Microsoft.Mcp.Core.Areas.Server.Commands.ToolLoading; public sealed class SingleProxyToolLoader( IMcpDiscoveryStrategy discoveryStrategy, ILogger logger, - IOptions options) : BaseToolLoader(logger) + IOptions options, + IOptions serverConfiguration) : BaseToolLoader(logger) { private readonly IMcpDiscoveryStrategy _discoveryStrategy = discoveryStrategy ?? throw new ArgumentNullException(nameof(discoveryStrategy)); + private readonly IOptions _options = options ?? throw new ArgumentNullException(nameof(options)); + private readonly string _toolName = serverConfiguration?.Value.ShortName ?? throw new ArgumentNullException(nameof(serverConfiguration)); + private readonly string _toolDescription = serverConfiguration!.Value.Description; + private readonly string _displayName = serverConfiguration!.Value.DisplayName; + private readonly JsonElement _toolSchema = BuildToolSchema(serverConfiguration!.Value.ShortName); + private string? _cachedRootToolsJson; private readonly ConcurrentDictionary _cachedToolListsJson = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary> _cachedAllToolLists = new(StringComparer.OrdinalIgnoreCase); - private readonly IOptions _options = options ?? throw new ArgumentNullException(nameof(options)); private const string ToolCallProxySchema = """ { @@ -41,36 +48,41 @@ public sealed class SingleProxyToolLoader( } """; - private static readonly JsonElement ToolSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "intent": { - "type": "string", - "description": "The intent of the azure operation to perform." - }, - "tool": { - "type": "string", - "description": "The azure tool to use to execute the operation." - }, - "command": { - "type": "string", - "description": "The command to execute against the specified tool." - }, - "parameters": { + private static JsonElement BuildToolSchema(string toolName) + { + var schemaJson = $$""" + { "type": "object", - "description": "The parameters to pass to the tool command." - }, - "learn": { - "type": "boolean", - "description": "To learn about the tool and its supported child tools and parameters.", - "default": false + "properties": { + "intent": { + "type": "string", + "description": "The intent of the {{toolName}} operation to perform." + }, + "tool": { + "type": "string", + "description": "The {{toolName}} tool to use to execute the operation." + }, + "command": { + "type": "string", + "description": "The command to execute against the specified tool." + }, + "parameters": { + "type": "object", + "description": "The parameters to pass to the tool command." + }, + "learn": { + "type": "boolean", + "description": "To learn about the tool and its supported child tools and parameters.", + "default": false + } + }, + "required": ["intent"], + "additionalProperties": false } - }, - "required": ["intent"], - "additionalProperties": false - } - """, ServerJsonContext.Default.JsonElement); + """; + + return JsonSerializer.Deserialize(schemaJson, ServerJsonContext.Default.JsonElement); + } public override ValueTask ListToolsHandler(RequestContext request, CancellationToken cancellationToken) { @@ -80,19 +92,10 @@ public override ValueTask ListToolsHandler(RequestContext - /// Handles invocation of the Azure proxy tool, routing requests to the correct Azure tool or command. + /// Handles invocation of the proxy tool, routing requests to the correct tool or command. /// /// The request context containing parameters and metadata. /// A cancellation token. @@ -436,7 +439,7 @@ await request.Server.NotifyProgressAsync(progressToken.Value, private async Task GetToolNameFromIntentAsync(RequestContext request, string intent, string toolsJson, CancellationToken cancellationToken) { - await NotifyProgressAsync(request, "Learning about Azure capabilities...", cancellationToken); + await NotifyProgressAsync(request, $"Learning about {_displayName} capabilities...", cancellationToken); var samplingRequest = new CreateMessageRequestParams { @@ -447,7 +450,7 @@ await request.Server.NotifyProgressAsync(progressToken.Value, Role = Role.Assistant, Content = [new TextContentBlock{ Text = $""" - The following is a list of available tools for the Azure server. + The following is a list of available tools for the {_displayName}. Your task: - Select a single tool that best matches the user's intent and return the name of the tool. diff --git a/core/Microsoft.Mcp.Core/src/Configuration/McpServerConfiguration.cs b/core/Microsoft.Mcp.Core/src/Configuration/McpServerConfiguration.cs index 097646ea32..0e7ff28c0a 100644 --- a/core/Microsoft.Mcp.Core/src/Configuration/McpServerConfiguration.cs +++ b/core/Microsoft.Mcp.Core/src/Configuration/McpServerConfiguration.cs @@ -18,6 +18,12 @@ public class McpServerConfiguration /// public required string Name { get; set; } + /// + /// A short identifier for the MCP server (i.e. "azure", "fabric"). + /// Used, for example, as the tool name when the server runs in single tool proxy mode. + /// + public required string ShortName { get; set; } + /// /// The display name of the MCP server. /// @@ -28,6 +34,11 @@ public class McpServerConfiguration /// public required string Version { get; set; } + /// + /// A description of the MCP server. Used, for example, as the tool description when the server runs in single tool proxy mode. + /// + public required string Description { get; set; } + /// /// Indicates whether telemetry is enabled for the MCP server. By default, it is set to true. /// diff --git a/core/Microsoft.Mcp.Core/src/Microsoft.Mcp.Core.csproj b/core/Microsoft.Mcp.Core/src/Microsoft.Mcp.Core.csproj index 1a2d433211..92f6f20ee0 100644 --- a/core/Microsoft.Mcp.Core/src/Microsoft.Mcp.Core.csproj +++ b/core/Microsoft.Mcp.Core/src/Microsoft.Mcp.Core.csproj @@ -18,6 +18,7 @@ + @@ -29,6 +30,7 @@ + diff --git a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs index 3dabff465c..0a9c3eca8f 100644 --- a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs +++ b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs @@ -24,9 +24,11 @@ public class TelemetryServiceTests private readonly McpServerConfiguration _testConfiguration = new() { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; private readonly IOptions _mockOptions; @@ -159,9 +161,11 @@ public async Task StartActivity_WithInvalidActivityName_ShouldHandleGracefully(s var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; @@ -191,9 +195,11 @@ public void StartActivity_WithoutInitialization_Throws() var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; @@ -224,9 +230,11 @@ public async Task StartActivity_WhenInitializationFails_Throws() var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; @@ -263,9 +271,11 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; var operationName = "an-activity-id"; @@ -297,9 +307,11 @@ public async Task InitializeAsync_InvokedOnce() var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; @@ -342,9 +354,11 @@ public async Task StartActivity_HasCloudBasedOnServiceStartOptions(string? cloud var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; var operationName = "an-activity-id"; @@ -385,9 +399,11 @@ public async Task StartActivity_HasCloudBasedOnConfiguration(string configName, var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; var operationName = "an-activity-id"; @@ -449,9 +465,11 @@ public async Task StartActivity_NoCloudWhenAzureCloudConfigurationIsNull() var configuration = new McpServerConfiguration { Name = "TestService", + ShortName = "test", Version = "1.0.0", IsTelemetryEnabled = true, DisplayName = "Test Display", + Description = "Test Description", RootCommandGroupName = "azmcp" }; var operationName = "an-activity-id"; diff --git a/servers/Azure.Mcp.Server/changelog-entries/1780345675706.yaml b/servers/Azure.Mcp.Server/changelog-entries/1780345675706.yaml new file mode 100644 index 0000000000..cff02d0106 --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1780345675706.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Breaking Changes" + description: "Changed appsetting.json property names to be scope to MicrosoftMcp instead of direct property names." diff --git a/servers/Azure.Mcp.Server/src/appsettings.json b/servers/Azure.Mcp.Server/src/appsettings.json index b744080665..cd32050d2b 100644 --- a/servers/Azure.Mcp.Server/src/appsettings.json +++ b/servers/Azure.Mcp.Server/src/appsettings.json @@ -1,5 +1,9 @@ { - "RootCommandGroupName": "azmcp", - "Name": "Azure.Mcp.Server", - "DisplayName": "Azure MCP Server" -} + "MicrosoftMcp": { + "RootCommandGroupName": "azmcp", + "Name": "Azure.Mcp.Server", + "ShortName": "azure", + "DisplayName": "Azure MCP Server", + "Description": "This server/tool provides real-time, programmatic access to all Azure products, services, and resources, as well as all interactions with the Azure Developer CLI (azd). Use this tool for any Azure control plane or data plane operation, including resource management and automation. To discover available capabilities, call the tool with the \"learn\" parameter to get a list of top-level tools. To explore further, set \"learn\" and specify a tool name to retrieve supported commands and their parameters. To execute an action, set the \"tool\", \"command\", and convert the user's intent into the \"parameters\" based on the discovered schema. Always use this tool for any Azure or \"azd\" related operation requiring up-to-date, dynamic, and interactive capabilities. Always include the \"intent\" parameter to specify the operation you want to perform." + } +} \ No newline at end of file diff --git a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.UnitTests/Infrastructure/VisualStudioToolNameTests.cs b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.UnitTests/Infrastructure/VisualStudioToolNameTests.cs index 195a2f71cc..b1220be1c9 100644 --- a/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.UnitTests/Infrastructure/VisualStudioToolNameTests.cs +++ b/servers/Azure.Mcp.Server/tests/Azure.Mcp.Server.UnitTests/Infrastructure/VisualStudioToolNameTests.cs @@ -60,8 +60,10 @@ private static Task> GetAllModeToolNamesAsync() var configurationOptions = Options.Create(new McpServerConfiguration { Name = "Test Server", + ShortName = "test", Version = "1.0", DisplayName = "Test", + Description = "Test Description", RootCommandGroupName = "azmcp" }); diff --git a/servers/Fabric.Mcp.Server/changelog-entries/1780345683191.yaml b/servers/Fabric.Mcp.Server/changelog-entries/1780345683191.yaml new file mode 100644 index 0000000000..cff02d0106 --- /dev/null +++ b/servers/Fabric.Mcp.Server/changelog-entries/1780345683191.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Breaking Changes" + description: "Changed appsetting.json property names to be scope to MicrosoftMcp instead of direct property names." diff --git a/servers/Fabric.Mcp.Server/src/appsettings.json b/servers/Fabric.Mcp.Server/src/appsettings.json index 93aa6e722e..d94146f139 100644 --- a/servers/Fabric.Mcp.Server/src/appsettings.json +++ b/servers/Fabric.Mcp.Server/src/appsettings.json @@ -1,5 +1,9 @@ { - "RootCommandGroupName": "fabmcp", - "Name": "Fabric.Mcp.Server", - "DisplayName": "Fabric MCP Server" -} + "MicrosoftMcp": { + "RootCommandGroupName": "fabmcp", + "Name": "Fabric.Mcp.Server", + "ShortName": "fabric", + "DisplayName": "Fabric MCP Server", + "Description": "This server/tool provides real-time, programmatic access to all Microsoft Fabric workspaces, items, and data platform capabilities. Use this tool for any Microsoft Fabric operation, including workspace, item, and data platform management and automation. To discover available capabilities, call the tool with the \"learn\" parameter to get a list of top-level tools. To explore further, set \"learn\" and specify a tool name to retrieve supported commands and their parameters. To execute an action, set the \"tool\", \"command\", and convert the user's intent into the \"parameters\" based on the discovered schema. Always use this tool for any Microsoft Fabric related operation requiring up-to-date, dynamic, and interactive capabilities. Always include the \"intent\" parameter to specify the operation you want to perform." + } +} \ No newline at end of file diff --git a/servers/Template.Mcp.Server/src/appsettings.json b/servers/Template.Mcp.Server/src/appsettings.json index f93719cbab..e6ac26f500 100644 --- a/servers/Template.Mcp.Server/src/appsettings.json +++ b/servers/Template.Mcp.Server/src/appsettings.json @@ -1,5 +1,9 @@ { - "RootCommandGroupName": "mcptmp", - "Name": "Template.Mcp.Server", - "DisplayName": "Template MCP Server" -} + "MicrosoftMcp": { + "RootCommandGroupName": "mcptmp", + "Name": "Template.Mcp.Server", + "ShortName": "template", + "DisplayName": "Template MCP Server", + "Description": "This server/tool provides real-time, programmatic access to the Template MCP Server capabilities. To discover available capabilities, call the tool with the \"learn\" parameter to get a list of top-level tools. To explore further, set \"learn\" and specify a tool name to retrieve supported commands and their parameters. To execute an action, set the \"tool\", \"command\", and convert the user's intent into the \"parameters\" based on the discovered schema. Always include the \"intent\" parameter to specify the operation you want to perform." + } +} \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/concepts/dotnet/azure-monitor-distro.md b/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/concepts/dotnet/azure-monitor-distro.md index 4c10bcb665..ee672f857e 100644 --- a/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/concepts/dotnet/azure-monitor-distro.md +++ b/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/concepts/dotnet/azure-monitor-distro.md @@ -83,7 +83,7 @@ builder.Services.AddOpenTelemetry().UseAzureMonitor(options => ## Package Reference ```xml - + ``` ## When to Use diff --git a/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/examples/dotnet/aspnetcore-distro-setup.md b/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/examples/dotnet/aspnetcore-distro-setup.md index b042c180ec..d98623238c 100644 --- a/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/examples/dotnet/aspnetcore-distro-setup.md +++ b/tools/Azure.Mcp.Tools.Monitor/src/Instrumentation/Resources/examples/dotnet/aspnetcore-distro-setup.md @@ -23,7 +23,7 @@ dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore Or in `.csproj`: ```xml - + ``` ## Step 2: Configure in Program.cs