| File: ApplicationModel\McpServerEndpointAnnotation.cs | Web Access |
| Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Hosting.ApplicationModel; /// <summary> /// Represents an annotation that identifies an endpoint on a resource that hosts a Model Context Protocol (MCP) server. /// </summary> /// <remarks> /// This annotation is intended for discovery and proxying scenarios where the Aspire AppHost can act as a mediator /// between clients (such as the Aspire CLI) and MCP servers exposed by resources. /// </remarks> public sealed class McpServerEndpointAnnotation : IResourceAnnotation { /// <summary> /// Initializes a new instance of the <see cref="McpServerEndpointAnnotation"/> class. /// </summary> /// <param name="endpointUrlResolver">A callback that resolves the MCP server endpoint URL from the resource.</param> public McpServerEndpointAnnotation(Func<IResourceWithEndpoints, CancellationToken, Task<Uri?>> endpointUrlResolver) { ArgumentNullException.ThrowIfNull(endpointUrlResolver); EndpointUrlResolver = endpointUrlResolver; } /// <summary> /// Gets the callback that resolves the MCP server endpoint URL from the resource. /// </summary> public Func<IResourceWithEndpoints, CancellationToken, Task<Uri?>> EndpointUrlResolver { get; } /// <summary> /// Creates an <see cref="McpServerEndpointAnnotation"/> that resolves the MCP server URL from a named endpoint. /// </summary> /// <param name="endpointName">The name of the endpoint on the resource that hosts the MCP server.</param> /// <param name="path">An optional path to append to the endpoint URL. Defaults to <c>"/mcp"</c>.</param> /// <returns>A new <see cref="McpServerEndpointAnnotation"/> instance.</returns> public static McpServerEndpointAnnotation FromEndpoint(string endpointName, string? path = "/mcp") { ArgumentException.ThrowIfNullOrWhiteSpace(endpointName); return new McpServerEndpointAnnotation(async (resource, cancellationToken) => { var endpoint = resource.GetEndpoint(endpointName); if (!endpoint.Exists) { return null; } var baseUrl = await endpoint.GetValueAsync(cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(baseUrl)) { return null; } if (string.IsNullOrEmpty(path)) { return new Uri(baseUrl, UriKind.Absolute); } var normalizedPath = path; if (!normalizedPath.StartsWith("/", StringComparison.Ordinal)) { normalizedPath = "/" + normalizedPath; } var combined = baseUrl.TrimEnd('/') + normalizedPath; return new Uri(combined, UriKind.Absolute); }); } }