diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/McpServerBuilderExtensions.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/McpServerBuilderExtensions.cs index d7c88bc6ffa6..23769319767a 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/McpServerBuilderExtensions.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Extensions/McpServerBuilderExtensions.cs @@ -60,23 +60,36 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, Kernel } /// - /// Adds a resource template to the server. + /// Adds a prompt definition and handlers for listing and reading prompts. /// /// The MCP server builder. - /// The resource template definition. + /// The prompt definition. /// The builder instance. - public static IMcpServerBuilder WithPrompt(this IMcpServerBuilder builder, PromptDefinition templateDefinition) + public static IMcpServerBuilder WithPrompt(this IMcpServerBuilder builder, PromptDefinition promptDefinition) { - PromptRegistry.RegisterPrompt(templateDefinition); + // Register the prompt definition in the DI container + builder.Services.AddSingleton(promptDefinition); + + builder.WithPromptHandlers(); + + return builder; + } - builder.WithListPromptsHandler(PromptRegistry.HandlerListPromptRequestsAsync); - builder.WithGetPromptHandler(PromptRegistry.HandlerGetPromptRequestsAsync); + /// + /// Adds handlers for listing and reading prompts. + /// + /// The MCP server builder. + /// The builder instance. + public static IMcpServerBuilder WithPromptHandlers(this IMcpServerBuilder builder) + { + builder.WithListPromptsHandler(HandleListPromptRequestsAsync); + builder.WithGetPromptHandler(HandleGetPromptRequestsAsync); return builder; } /// - /// Adds a resource template to the server. + /// Adds a resource template and handlers for listing and reading resource templates. /// /// The MCP server builder. /// The kernel instance. @@ -95,23 +108,36 @@ public static IMcpServerBuilder WithResourceTemplate( } /// - /// Adds a resource template to the server. + /// Adds a resource template and handlers for listing and reading resource templates. /// /// The MCP server builder. /// The resource template definition. /// The builder instance. public static IMcpServerBuilder WithResourceTemplate(this IMcpServerBuilder builder, ResourceTemplateDefinition templateDefinition) { - ResourceRegistry.RegisterResourceTemplate(templateDefinition); + // Register the resource template definition in the DI container + builder.Services.AddSingleton(templateDefinition); + + builder.WithResourceTemplateHandlers(); + + return builder; + } - builder.WithListResourceTemplatesHandler(ResourceRegistry.HandleListResourceTemplatesRequestAsync); - builder.WithReadResourceHandler(ResourceRegistry.HandleReadResourceRequestAsync); + /// + /// Adds handlers for listing and reading resource templates. + /// + /// The MCP server builder. + /// The builder instance. + public static IMcpServerBuilder WithResourceTemplateHandlers(this IMcpServerBuilder builder) + { + builder.WithListResourceTemplatesHandler(HandleListResourceTemplatesRequestAsync); + builder.WithReadResourceHandler(HandleReadResourceRequestAsync); return builder; } /// - /// Adds a resource to the server. + /// Adds a resource and handlers for listing and reading resources. /// /// The MCP server builder. /// The kernel instance. @@ -130,18 +156,117 @@ public static IMcpServerBuilder WithResource( } /// - /// Adds a resource to the server. + /// Adds a resource and handlers for listing and reading resources. /// /// The MCP server builder. /// The resource definition. /// The builder instance. public static IMcpServerBuilder WithResource(this IMcpServerBuilder builder, ResourceDefinition resourceDefinition) { - ResourceRegistry.RegisterResource(resourceDefinition); + // Register the resource definition in the DI container + builder.Services.AddSingleton(resourceDefinition); + + builder.WithResourceHandlers(); + + return builder; + } - builder.WithListResourcesHandler(ResourceRegistry.HandleListResourcesRequestAsync); - builder.WithReadResourceHandler(ResourceRegistry.HandleReadResourceRequestAsync); + /// + /// Adds handlers for listing and reading resources. + /// + /// The MCP server builder. + /// The builder instance. + public static IMcpServerBuilder WithResourceHandlers(this IMcpServerBuilder builder) + { + builder.WithListResourcesHandler(HandleListResourcesRequestAsync); + builder.WithReadResourceHandler(HandleReadResourceRequestAsync); return builder; } + + private static Task HandleListPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + { + // Get and return all prompt definitions registered in the DI container + IEnumerable promptDefinitions = context.Server.Services!.GetServices(); + + return Task.FromResult(new ListPromptsResult + { + Prompts = [.. promptDefinitions.Select(d => d.Prompt)] + }); + } + + private static async Task HandleGetPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) + { + // Make sure the prompt name is provided + if (context.Params?.Name is not string { } promptName || string.IsNullOrEmpty(promptName)) + { + throw new ArgumentException("Prompt name is required."); + } + + // Get all prompt definitions registered in the DI container + IEnumerable promptDefinitions = context.Server.Services!.GetServices(); + + // Look up the prompt definition + PromptDefinition? definition = promptDefinitions.FirstOrDefault(d => d.Prompt.Name == promptName); + if (definition is null) + { + throw new ArgumentException($"No handler found for the prompt '{promptName}'."); + } + + // Invoke the handler + return await definition.Handler(context, cancellationToken); + } + + private static Task HandleReadResourceRequestAsync(RequestContext context, CancellationToken cancellationToken) + { + // Make sure the uri of the resource or resource template is provided + if (context.Params?.Uri is not string { } resourceUri || string.IsNullOrEmpty(resourceUri)) + { + throw new ArgumentException("Resource uri is required."); + } + + // Look up in registered resource first + IEnumerable resourceDefinitions = context.Server.Services!.GetServices(); + + ResourceDefinition? resourceDefinition = resourceDefinitions.FirstOrDefault(d => d.Resource.Uri == resourceUri); + if (resourceDefinition is not null) + { + return resourceDefinition.InvokeHandlerAsync(context, cancellationToken); + } + + // Look up in registered resource templates + IEnumerable resourceTemplateDefinitions = context.Server.Services!.GetServices(); + + foreach (var resourceTemplateDefinition in resourceTemplateDefinitions) + { + if (resourceTemplateDefinition.IsMatch(resourceUri)) + { + return resourceTemplateDefinition.InvokeHandlerAsync(context, cancellationToken); + } + } + + throw new ArgumentException($"No handler found for the resource uri '{resourceUri}'."); + } + + private static Task HandleListResourceTemplatesRequestAsync(RequestContext context, CancellationToken cancellationToken) + { + // Get and return all resource template definitions registered in the DI container + IEnumerable definitions = context.Server.Services!.GetServices(); + + return Task.FromResult(new ListResourceTemplatesResult + { + ResourceTemplates = [.. definitions.Select(d => d.ResourceTemplate)] + }); + } + + private static Task HandleListResourcesRequestAsync(RequestContext context, CancellationToken cancellationToken) + { + // Get and return all resource template definitions registered in the DI container + IEnumerable definitions = context.Server.Services!.GetServices(); + + return Task.FromResult(new ListResourcesResult + { + Resources = [.. definitions.Select(d => d.Resource)] + }); + } } diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs deleted file mode 100644 index 062983dab3ba..000000000000 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Prompts/PromptRegistry.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using ModelContextProtocol.Protocol.Types; -using ModelContextProtocol.Server; - -namespace MCPServer.Prompts; - -/// -/// Represents the prompt registry that contains the prompt definitions and provides the handlers for the prompt `List` and `Get` requests. -/// -internal static class PromptRegistry -{ - private static readonly Dictionary s_definitions = new(); - - /// - /// Registers a prompt definition. - /// - /// The prompt definition to register. - public static void RegisterPrompt(PromptDefinition definition) - { - if (s_definitions.ContainsKey(definition.Prompt.Name)) - { - throw new ArgumentException($"A prompt with the name '{definition.Prompt.Name}' is already registered."); - } - - s_definitions[definition.Prompt.Name] = definition; - } - - /// - /// Handles the `Get` prompt requests. - /// - /// The request context. - /// The cancellation token. - /// The result of the `Get` prompt request. - public static async Task HandlerGetPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) - { - // Make sure the prompt name is provided - if (context.Params?.Name is not string { } promptName || string.IsNullOrEmpty(promptName)) - { - throw new ArgumentException("Prompt name is required."); - } - - // Look up the prompt handler - if (!s_definitions.TryGetValue(promptName, out PromptDefinition? definition)) - { - throw new ArgumentException($"No handler found for the prompt '{promptName}'."); - } - - // Invoke the handler - return await definition.Handler(context, cancellationToken); - } - - /// - /// Handles the `List` prompt requests. - /// - /// Context of the request. - /// The cancellation token. - /// The result of the `List` prompt request. - public static Task HandlerListPromptRequestsAsync(RequestContext context, CancellationToken cancellationToken) - { - return Task.FromResult(new ListPromptsResult - { - Prompts = [.. s_definitions.Values.Select(d => d.Prompt)] - }); - } -} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceRegistry.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceRegistry.cs deleted file mode 100644 index ea2a89eef594..000000000000 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceRegistry.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using ModelContextProtocol.Protocol.Types; -using ModelContextProtocol.Server; - -namespace MCPServer.Resources; - -/// -/// Represents the resource registry that contains the resource and resource template definitions and provides the handlers for the `List` and `Get` requests. -/// -internal static class ResourceRegistry -{ - private static readonly Dictionary s_resourceDefinitions = []; - - private static readonly IList s_resourceTemplateDefinitions = []; - - /// - /// Registers a resource. - /// - /// The resource to register. - public static void RegisterResource(ResourceDefinition definition) - { - if (s_resourceDefinitions.ContainsKey(definition.Resource.Uri)) - { - throw new ArgumentException($"A resource with the uri '{definition.Resource.Uri}' is already registered."); - } - - s_resourceDefinitions[definition.Resource.Uri] = definition; - } - - /// - /// Registers a resource template. - /// - /// The resource template to register. - public static void RegisterResourceTemplate(ResourceTemplateDefinition definition) - { - if (s_resourceTemplateDefinitions.Any(d => d.ResourceTemplate.UriTemplate == definition.ResourceTemplate.UriTemplate)) - { - throw new ArgumentException($"A resource template with the uri template '{definition.ResourceTemplate.UriTemplate}' is already registered."); - } - - s_resourceTemplateDefinitions.Add(definition); - } - - /// - /// Handles the `ListResourceTemplates` request. - /// - /// The MCP server context. - /// The cancellation token. - /// The result of the request. - public static Task HandleListResourceTemplatesRequestAsync(RequestContext context, CancellationToken cancellationToken) - { - return Task.FromResult(new ListResourceTemplatesResult - { - ResourceTemplates = [.. s_resourceTemplateDefinitions.Select(d => d.ResourceTemplate)] - }); - } - - /// - /// Handles the `ListResources` request. - /// - /// The MCP server context. - /// The cancellation token. - /// The result of the request. - public static Task HandleListResourcesRequestAsync(RequestContext context, CancellationToken cancellationToken) - { - return Task.FromResult(new ListResourcesResult - { - Resources = [.. s_resourceDefinitions.Values.Select(d => d.Resource)] - }); - } - - /// - /// Handles the `ReadResource` request. - /// - /// The MCP server context. - /// The cancellation token. - /// The result of the request. - public static Task HandleReadResourceRequestAsync(RequestContext context, CancellationToken cancellationToken) - { - // Make sure the uri of the resource or resource template is provided - if (context.Params?.Uri is not string { } resourceUri || string.IsNullOrEmpty(resourceUri)) - { - throw new ArgumentException("Resource uri is required."); - } - - // Look up in registered resource first - if (s_resourceDefinitions.TryGetValue(resourceUri, out ResourceDefinition? resourceDefinition)) - { - return resourceDefinition.InvokeHandlerAsync(context, cancellationToken); - } - - // Look up in registered resource templates - foreach (var resourceTemplateDefinition in s_resourceTemplateDefinitions) - { - if (resourceTemplateDefinition.IsMatch(resourceUri)) - { - return resourceTemplateDefinition.InvokeHandlerAsync(context, cancellationToken); - } - } - - throw new ArgumentException($"No handler found for the resource uri '{resourceUri}'."); - } -} diff --git a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceTemplateDefinition.cs b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceTemplateDefinition.cs index f3f7736f5f9f..d7455c3a7010 100644 --- a/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceTemplateDefinition.cs +++ b/dotnet/samples/Demos/ModelContextProtocolClientServer/MCPServer/Resources/ResourceTemplateDefinition.cs @@ -57,10 +57,6 @@ public async Task InvokeHandlerAsync(RequestContext() - ?? throw new InvalidOperationException("Kernel is not available."); - this.Kernel ??= context.Server.Services?.GetRequiredService() ?? throw new InvalidOperationException("Kernel is not available.");