Rename project and namespaces
This commit is contained in:
parent
e7fa6194f6
commit
c9ed453745
10 changed files with 426 additions and 918 deletions
69
src/ModelContextProtocol/Server/IMcpServer.cs
Normal file
69
src/ModelContextProtocol/Server/IMcpServer.cs
Normal 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);
|
||||
}
|
||||
207
src/ModelContextProtocol/Server/McpServer.cs
Normal file
207
src/ModelContextProtocol/Server/McpServer.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
48
src/ModelContextProtocol/Server/McpServerException.cs
Normal file
48
src/ModelContextProtocol/Server/McpServerException.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
237
src/ModelContextProtocol/Server/McpServerExtensions.cs
Normal file
237
src/ModelContextProtocol/Server/McpServerExtensions.cs
Normal 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
|
||||
}
|
||||
}
|
||||
36
src/ModelContextProtocol/Server/McpServerFactory.cs
Normal file
36
src/ModelContextProtocol/Server/McpServerFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
137
src/ModelContextProtocol/Server/McpServerHandlers.cs
Normal file
137
src/ModelContextProtocol/Server/McpServerHandlers.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
44
src/ModelContextProtocol/Server/McpServerOptions.cs
Normal file
44
src/ModelContextProtocol/Server/McpServerOptions.cs
Normal 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; }
|
||||
}
|
||||
20
src/ModelContextProtocol/Server/McpToolAttribute.cs
Normal file
20
src/ModelContextProtocol/Server/McpToolAttribute.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
7
src/ModelContextProtocol/Server/McpToolTypeAttribute.cs
Normal file
7
src/ModelContextProtocol/Server/McpToolTypeAttribute.cs
Normal 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;
|
||||
10
src/ModelContextProtocol/Server/RequestContext.cs
Normal file
10
src/ModelContextProtocol/Server/RequestContext.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue