Rename project and namespaces

This commit is contained in:
Eirik Tsarpalis 2025-03-19 10:51:27 +02:00
commit c9ed453745
10 changed files with 426 additions and 918 deletions

View file

@ -0,0 +1,69 @@
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Types;
namespace ModelContextProtocol.Server;
/// <summary>
/// Represents a server that can communicate with a client using the MCP protocol.
/// </summary>
public interface IMcpServer : IAsyncDisposable
{
/// <summary>
/// Gets a value indicating whether the server has been initialized.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets the capabilities supported by the client.
/// </summary>
ClientCapabilities? ClientCapabilities { get; }
/// <summary>
/// Gets the version and implementation information of the client.
/// </summary>
Implementation? ClientInfo { get; }
/// <summary>
/// Gets the service provider for the server.
/// </summary>
IServiceProvider? ServiceProvider { get; }
/// <summary>
/// Adds a handler for client notifications of a specific method.
/// </summary>
/// <param name="method">The notification method to handle.</param>
/// <param name="handler">The async handler function to process notifications.</param>
/// <remarks>
/// <para>
/// Each method may have multiple handlers. Adding a handler for a method that already has one
/// will not replace the existing handler.
/// </para>
/// <para>
/// <see cref="NotificationMethods"> provides constants for common notification methods.</see>
/// </para>
/// </remarks>
void AddNotificationHandler(string method, Func<JsonRpcNotification, Task> handler);
/// <summary>
/// Starts the server and begins listening for client requests.
/// </summary>
Task StartAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sends a generic JSON-RPC request to the client.
/// NB! This is a temporary method that is available to send not yet implemented feature messages.
/// Once all MCP features are implemented this will be made private, as it is purely a convenience for those who wish to implement features ahead of the library.
/// </summary>
/// <typeparam name="T">The expected response type.</typeparam>
/// <param name="request">The JSON-RPC request to send.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task containing the client's response.</returns>
Task<T> SendRequestAsync<T>(JsonRpcRequest request, CancellationToken cancellationToken) where T : class;
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,207 @@
using ModelContextProtocol.Logging;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Shared;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Server;
/// <inheritdoc />
internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
{
private readonly IServerTransport? _serverTransport;
private readonly McpServerOptions _options;
private volatile bool _isInitializing;
private readonly ILogger _logger;
/// <summary>
/// Creates a new instance of <see cref="McpServer"/>.
/// </summary>
/// <param name="transport">Transport to use for the server</param>
/// <param name="options">Configuration options for this server, including capabilities.
/// Make sure to accurately reflect exactly what capabilities the server supports and does not support.</param>
/// <param name="loggerFactory">Logger factory to use for logging</param>
/// <param name="serviceProvider">Optional service provider to use for dependency injection</param>
/// <exception cref="McpServerException"></exception>
public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider)
: base(transport, loggerFactory)
{
Throw.IfNull(options);
_serverTransport = transport as IServerTransport;
_options = options;
_logger = (ILogger?)loggerFactory?.CreateLogger<McpServer>() ?? NullLogger.Instance;
ServerInstructions = options.ServerInstructions;
ServiceProvider = serviceProvider;
AddNotificationHandler("notifications/initialized", _ =>
{
IsInitialized = true;
return Task.CompletedTask;
});
SetInitializeHandler(options);
SetCompletionHandler(options);
SetPingHandler();
SetToolsHandler(options);
SetPromptsHandler(options);
SetResourcesHandler(options);
}
public ClientCapabilities? ClientCapabilities { get; set; }
/// <inheritdoc />
public Implementation? ClientInfo { get; set; }
/// <inheritdoc />
public string? ServerInstructions { get; set; }
/// <inheritdoc />
public IServiceProvider? ServiceProvider { get; }
/// <inheritdoc />
public override string EndpointName =>
$"Server ({_options.ServerInfo.Name} {_options.ServerInfo.Version}), Client ({ClientInfo?.Name} {ClientInfo?.Version})";
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken = default)
{
if (_isInitializing)
{
_logger.ServerAlreadyInitializing(EndpointName);
throw new InvalidOperationException("Server is already initializing");
}
_isInitializing = true;
if (IsInitialized)
{
_logger.ServerAlreadyInitializing(EndpointName);
return;
}
try
{
CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
if (_serverTransport is not null)
{
// Start listening for messages
await _serverTransport.StartListeningAsync(CancellationTokenSource.Token).ConfigureAwait(false);
}
// Start processing messages
MessageProcessingTask = ProcessMessagesAsync(CancellationTokenSource.Token);
// Unlike McpClient, we're not done initializing until we've received a message from the client, so we don't set IsInitialized here
}
catch (Exception e)
{
_logger.ServerInitializationError(EndpointName, e);
await CleanupAsync().ConfigureAwait(false);
throw;
}
}
private void SetPingHandler()
{
SetRequestHandler<JsonNode, PingResult>("ping",
(request, _) => Task.FromResult(new PingResult()));
}
private void SetInitializeHandler(McpServerOptions options)
{
SetRequestHandler<InitializeRequestParams, InitializeResult>("initialize",
(request, _) =>
{
ClientCapabilities = request?.Capabilities ?? new();
ClientInfo = request?.ClientInfo;
return Task.FromResult(new InitializeResult()
{
ProtocolVersion = options.ProtocolVersion,
Instructions = ServerInstructions,
ServerInfo = _options.ServerInfo,
Capabilities = options.Capabilities ?? new ServerCapabilities(),
});
});
}
private void SetCompletionHandler(McpServerOptions options)
{
// This capability is not optional, so return an empty result if there is no handler.
SetRequestHandler<CompleteRequestParams, CompleteResult>("completion/complete",
options.GetCompletionHandler is { } handler ?
(request, ct) => handler(new(this, request), ct) :
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }));
}
private void SetResourcesHandler(McpServerOptions options)
{
if (options.Capabilities?.Resources is not { } resourcesCapability)
{
return;
}
if (resourcesCapability.ListResourcesHandler is not { } listResourcesHandler ||
resourcesCapability.ReadResourceHandler is not { } readResourceHandler)
{
throw new McpServerException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified.");
}
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>("resources/list", (request, ct) => listResourcesHandler(new(this, request), ct));
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>("resources/read", (request, ct) => readResourceHandler(new(this, request), ct));
if (resourcesCapability.Subscribe is not true)
{
return;
}
var subscribeHandler = resourcesCapability.SubscribeToResourcesHandler;
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler;
if (subscribeHandler is null || unsubscribeHandler is null)
{
throw new McpServerException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
}
SetRequestHandler<SubscribeRequestParams, EmptyResult>("resources/subscribe", (request, ct) => subscribeHandler(new(this, request), ct));
SetRequestHandler<UnsubscribeRequestParams, EmptyResult>("resources/unsubscribe", (request, ct) => unsubscribeHandler(new(this, request), ct));
}
private void SetPromptsHandler(McpServerOptions options)
{
if (options.Capabilities?.Prompts is not { } promptsCapability)
{
return;
}
if (promptsCapability.ListPromptsHandler is not { } listPromptsHandler ||
promptsCapability.GetPromptHandler is not { } getPromptHandler)
{
throw new McpServerException("Prompts capability was enabled, but ListPrompts and/or GetPrompt handlers were not specified.");
}
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>("prompts/list", (request, ct) => listPromptsHandler(new(this, request), ct));
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get", (request, ct) => getPromptHandler(new(this, request), ct));
}
private void SetToolsHandler(McpServerOptions options)
{
if (options.Capabilities?.Tools is not { } toolsCapability)
{
return;
}
if (toolsCapability.ListToolsHandler is not { } listToolsHandler ||
toolsCapability.CallToolHandler is not { } callToolHandler)
{
throw new McpServerException("ListTools and/or CallTool handlers were specified but the Tools capability was not enabled.");
}
SetRequestHandler<ListToolsRequestParams, ListToolsResult>("tools/list", (request, ct) => listToolsHandler(new(this, request), ct));
SetRequestHandler<CallToolRequestParams, CallToolResponse>("tools/call", (request, ct) => callToolHandler(new(this, request), ct));
}
}

View file

@ -0,0 +1,48 @@
using ModelContextProtocol.Client;
namespace ModelContextProtocol.Server;
/// <summary>
/// Represents errors that occur in the MCP server.
/// </summary>
public class McpServerException : Exception
{
/// <summary>
/// Gets the error code if this exception was caused by a JSON-RPC error response.
/// </summary>
public int? ErrorCode { get; }
/// <summary>
/// Initializes a new instance of the <see cref="McpServerException"/> class with a specified error message.
/// </summary>
public McpServerException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpServerException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public McpServerException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpServerException"/> class with a specified error message and error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorCode">The error code associated with the JSON-RPC error response.</param>
public McpServerException(string message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public McpServerException(string message, Exception innerException) : base(message, innerException)
{
}
}

View file

@ -0,0 +1,237 @@
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Text;
namespace ModelContextProtocol.Server;
/// <inheritdoc />
public static class McpServerExtensions
{
/// <summary>
/// Requests to sample an LLM via the client.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The client does not support sampling.</exception>
public static Task<CreateMessageResult> RequestSamplingAsync(
this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken)
{
Throw.IfNull(server);
if (server.ClientCapabilities?.Sampling is null)
{
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
}
return server.SendRequestAsync<CreateMessageResult>(
new JsonRpcRequest { Method = "sampling/createMessage", Params = request },
cancellationToken);
}
/// <summary>
/// Requests to sample an LLM via the client.
/// </summary>
/// <param name="server">The server issueing the request.</param>
/// <param name="messages">The messages to send as part of the request.</param>
/// <param name="options">The options to use for the request.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task containing the response from the client.</returns>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="messages"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The client does not support sampling.</exception>
public static async Task<ChatResponse> RequestSamplingAsync(
this IMcpServer server,
IEnumerable<ChatMessage> messages, ChatOptions? options = default, CancellationToken cancellationToken = default)
{
Throw.IfNull(server);
Throw.IfNull(messages);
StringBuilder? systemPrompt = null;
List<SamplingMessage> samplingMessages = [];
foreach (var message in messages)
{
if (message.Role == ChatRole.System)
{
if (systemPrompt is null)
{
systemPrompt = new();
}
else
{
systemPrompt.AppendLine();
}
systemPrompt.Append(message.Text);
continue;
}
if (message.Role == ChatRole.User || message.Role == ChatRole.Assistant)
{
Role role = message.Role == ChatRole.User ? Role.User : Role.Assistant;
foreach (var content in message.Contents)
{
switch (content)
{
case TextContent textContent:
samplingMessages.Add(new()
{
Role = role,
Content = new()
{
Type = "text",
Text = textContent.Text,
},
});
break;
case DataContent dataContent when dataContent.HasTopLevelMediaType("image"):
samplingMessages.Add(new()
{
Role = role,
Content = new()
{
Type = "image",
MimeType = dataContent.MediaType,
Data = Convert.ToBase64String(dataContent.Data.
#if NET
Span),
#else
ToArray()),
#endif
},
});
break;
}
}
}
}
ModelPreferences? modelPreferences = null;
if (options?.ModelId is { } modelId)
{
modelPreferences = new() { Hints = [new() { Name = modelId }] };
}
var result = await server.RequestSamplingAsync(new()
{
Messages = samplingMessages,
MaxTokens = options?.MaxOutputTokens,
StopSequences = options?.StopSequences?.ToArray(),
SystemPrompt = systemPrompt?.ToString(),
Temperature = options?.Temperature,
ModelPreferences = modelPreferences,
}, cancellationToken).ConfigureAwait(false);
ChatMessage responseMessage = new()
{
Role = result.Role == "user" ? ChatRole.User : ChatRole.Assistant
};
if (result.Content is { Type: "text" })
{
responseMessage.Contents.Add(new TextContent(result.Content.Text));
}
else if (result.Content is { Type: "image", MimeType: not null, Data: not null })
{
responseMessage.Contents.Add(new DataContent(Convert.FromBase64String(result.Content.Data), result.Content.MimeType));
}
else if (result.Content is { Type: "resource" } && result.Content.Resource is { } resourceContents)
{
if (resourceContents.Text is not null)
{
responseMessage.Contents.Add(new TextContent(resourceContents.Text));
}
if (resourceContents.Blob is not null && resourceContents.MimeType is not null)
{
responseMessage.Contents.Add(new DataContent(Convert.FromBase64String(resourceContents.Blob), resourceContents.MimeType));
}
}
return new(responseMessage)
{
ModelId = result.Model,
FinishReason = result.StopReason switch
{
"maxTokens" => ChatFinishReason.Length,
"endTurn" or "stopSequence" or _ => ChatFinishReason.Stop,
}
};
}
/// <summary>Creates an <see cref="IChatClient"/> that can be used to send sampling requests to the client.</summary>
/// <param name="server">The server to be wrapped as an <see cref="IChatClient"/>.</param>
/// <returns>The <see cref="IChatClient"/> that can be used to issue sampling requests to the client.</returns>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The client does not support sampling.</exception>
public static IChatClient AsSamplingChatClient(this IMcpServer server)
{
Throw.IfNull(server);
if (server.ClientCapabilities?.Sampling is null)
{
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
}
return new SamplingChatClient(server);
}
/// <summary>
/// Requests the client to list the roots it exposes.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">The client does not support roots.</exception>
public static Task<ListRootsResult> RequestRootsAsync(
this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken)
{
Throw.IfNull(server);
if (server.ClientCapabilities?.Roots is null)
{
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
}
return server.SendRequestAsync<ListRootsResult>(
new JsonRpcRequest { Method = "roots/list", Params = request },
cancellationToken);
}
/// <summary>Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.</summary>
/// <param name="server"></param>
private sealed class SamplingChatClient(IMcpServer server) : IChatClient
{
/// <inheritdoc/>
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) =>
server.RequestSamplingAsync(messages, options, cancellationToken);
/// <inheritdoc/>
async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages, ChatOptions? options, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var response = await GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
foreach (var update in response.ToChatResponseUpdates())
{
yield return update;
}
}
/// <inheritdoc/>
object? IChatClient.GetService(Type serviceType, object? serviceKey)
{
Throw.IfNull(serviceType);
return
serviceKey is not null ? null :
serviceType.IsInstanceOfType(this) ? this :
serviceType.IsInstanceOfType(server) ? server :
null;
}
/// <inheritdoc/>
void IDisposable.Dispose() { } // nop
}
}

View file

@ -0,0 +1,36 @@
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.Logging;
namespace ModelContextProtocol.Server;
/// <summary>
/// Provides a factory for creating <see cref="IMcpServer"/> instances.
/// </summary>
public static class McpServerFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="McpServerFactory"/> class.
/// </summary>
/// <param name="serverTransport">Transport to use for the server</param>
/// <param name="serverOptions">
/// Configuration options for this server, including capabilities.
/// Make sure to accurately reflect exactly what capabilities the server supports and does not support.
/// </param>
/// <param name="serviceProvider">Optional service provider to create new instances.</param>
/// <param name="loggerFactory">Logger factory to use for logging</param>
/// <returns>An <see cref="McpServer"/> that's started and ready to receive connections.</returns>
/// <exception cref="ArgumentNullException"><paramref name="serverTransport"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/>.</exception>
public static IMcpServer Create(
ITransport serverTransport,
McpServerOptions serverOptions,
ILoggerFactory? loggerFactory = null,
IServiceProvider? serviceProvider = null)
{
Throw.IfNull(serverTransport);
Throw.IfNull(serverOptions);
return new McpServer(serverTransport, serverOptions, loggerFactory, serviceProvider);
}
}

View file

@ -0,0 +1,137 @@
using ModelContextProtocol.Protocol.Types;
namespace ModelContextProtocol.Server;
/// <summary>
/// Container for handlers used in the creation of an MCP server.
/// </summary>
public sealed class McpServerHandlers
{
/// <summary>
/// Gets or sets the handler for list tools requests.
/// </summary>
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for call tool requests.
/// </summary>
public Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
/// <summary>
/// Gets or sets the handler for list prompts requests.
/// </summary>
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for get prompt requests.
/// </summary>
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
/// <summary>
/// Gets or sets the handler for list resources requests.
/// </summary>
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for read resources requests.
/// </summary>
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
/// <summary>
/// Gets or sets the handler for get completion requests.
/// </summary>
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
/// <summary>
/// Gets or sets the handler for subscribe to resources messages.
/// </summary>
public Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>>? SubscribeToResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for unsubscribe from resources messages.
/// </summary>
public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }
/// <summary>
/// Overwrite any handlers in McpServerOptions with non-null handlers from this instance.
/// </summary>
/// <param name="options"></param>
/// <returns></returns>
internal void OverwriteWithSetHandlers(McpServerOptions options)
{
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
if (ListPromptsHandler is not null || GetPromptHandler is not null)
{
promptsCapability = promptsCapability is null ?
new()
{
ListPromptsHandler = ListPromptsHandler,
GetPromptHandler = GetPromptHandler,
} :
promptsCapability with
{
ListPromptsHandler = ListPromptsHandler ?? promptsCapability.ListPromptsHandler,
GetPromptHandler = GetPromptHandler ?? promptsCapability.GetPromptHandler,
};
}
ResourcesCapability? resourcesCapability = options.Capabilities?.Resources;
if (ListResourcesHandler is not null ||
ReadResourceHandler is not null)
{
resourcesCapability = resourcesCapability is null ?
new()
{
ListResourcesHandler = ListResourcesHandler,
ReadResourceHandler = ReadResourceHandler
} :
resourcesCapability with
{
ListResourcesHandler = ListResourcesHandler ?? resourcesCapability.ListResourcesHandler,
ReadResourceHandler = ReadResourceHandler ?? resourcesCapability.ReadResourceHandler
};
if (SubscribeToResourcesHandler is not null || UnsubscribeFromResourcesHandler is not null)
{
resourcesCapability = resourcesCapability with
{
SubscribeToResourcesHandler = SubscribeToResourcesHandler ?? resourcesCapability.SubscribeToResourcesHandler,
UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler ?? resourcesCapability.UnsubscribeFromResourcesHandler,
Subscribe = true
};
}
}
ToolsCapability? toolsCapability = options.Capabilities?.Tools;
if (ListToolsHandler is not null || CallToolHandler is not null)
{
toolsCapability = toolsCapability is null ?
new()
{
ListToolsHandler = ListToolsHandler,
CallToolHandler = CallToolHandler,
} :
toolsCapability with
{
ListToolsHandler = ListToolsHandler ?? toolsCapability.ListToolsHandler,
CallToolHandler = CallToolHandler ?? toolsCapability.CallToolHandler,
};
}
options.Capabilities = options.Capabilities is null ?
new()
{
Prompts = promptsCapability,
Resources = resourcesCapability,
Tools = toolsCapability,
} :
options.Capabilities with
{
Prompts = promptsCapability,
Resources = resourcesCapability,
Tools = toolsCapability,
};
options.GetCompletionHandler = GetCompletionHandler ?? options.GetCompletionHandler;
}
}

View file

@ -0,0 +1,44 @@

using ModelContextProtocol.Protocol.Types;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Server;
/// <summary>
/// Configuration options for the MCP server. This is passed to the client during the initialization sequence, letting it know about the server's capabilities and
/// protocol version.
/// <see href="https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/lifecycle/">See the protocol specification for details on capability negotiation</see>
/// </summary>
public class McpServerOptions
{
/// <summary>
/// Information about this server implementation.
/// </summary>
public required Implementation ServerInfo { get; set; }
/// <summary>
/// Server capabilities to advertise to the server.
/// </summary>
public ServerCapabilities? Capabilities { get; set; }
/// <summary>
/// Protocol version to request from the server.
/// </summary>
public string ProtocolVersion { get; set; } = "2024-11-05";
/// <summary>
/// Timeout for initialization sequence.
/// </summary>
public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(60);
/// <summary>
/// Optional server instructions to send to clients
/// </summary>
public string ServerInstructions { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the handler for get completion requests.
/// </summary>
[JsonIgnore]
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
}

View file

@ -0,0 +1,20 @@
namespace ModelContextProtocol.Server;
/// <summary>
/// Attribute to mark a method as an MCP tool.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class McpToolAttribute : Attribute
{
/// <summary>Gets the name of the tool.</summary>
/// <remarks>If not provided, the method name will be used.</remarks>
public string? Name { get; }
/// <summary>
/// Attribute to mark a method as an MCP tool.
/// </summary>
/// <param name="name">The name of the tool. If not provided, the class name will be used.</param>
public McpToolAttribute(string? name = null)
{
Name = name;
}
}

View file

@ -0,0 +1,7 @@
namespace ModelContextProtocol.Server;
/// <summary>
/// Attribute to mark a type as container for MCP tools.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class McpToolTypeAttribute : Attribute;

View file

@ -0,0 +1,10 @@
namespace ModelContextProtocol.Server;
/// <summary>
/// Container for the request context.
/// </summary>
/// <typeparam name="TParams">Type of the request parameters</typeparam>
public record RequestContext<TParams>(IMcpServer Server, TParams? Params)
{
}