Rename project and namespaces
This commit is contained in:
parent
e7fa6194f6
commit
c9ed453745
8 changed files with 426 additions and 918 deletions
|
|
@ -0,0 +1,25 @@
|
|||
using ModelContextProtocol.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ModelContextProtocol.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IMcpServerBuilder"/>.
|
||||
/// </summary>
|
||||
internal class DefaultMcpServerBuilder : IMcpServerBuilder
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultMcpServerBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public DefaultMcpServerBuilder(IServiceCollection services)
|
||||
{
|
||||
Throw.IfNull(services);
|
||||
|
||||
Services = services;
|
||||
}
|
||||
}
|
||||
15
src/ModelContextProtocol/Configuration/IMcpServerBuilder.cs
Normal file
15
src/ModelContextProtocol/Configuration/IMcpServerBuilder.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using ModelContextProtocol.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ModelContextProtocol.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Builder for configuring <see cref="IMcpServer"/> instances.
|
||||
/// </summary>
|
||||
public interface IMcpServerBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the service collection.
|
||||
/// </summary>
|
||||
IServiceCollection Services { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
using ModelContextProtocol.Configuration;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Extension to configure the MCP server with handlers
|
||||
/// </summary>
|
||||
public static partial class McpServerBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the handler for list tools requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.ListToolsHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for call tool requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.CallToolHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for list prompts requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.ListPromptsHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for get prompt requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.GetPromptHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for list resources requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.ListResourcesHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for read resources requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.ReadResourceHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for get completion requests.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.GetCompletionHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.SubscribeToResourcesHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="handler">The handler.</param>
|
||||
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.Configure<McpServerHandlers>(s => s.UnsubscribeFromResourcesHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
using ModelContextProtocol.Configuration;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Extension to configure the MCP server with tools
|
||||
/// </summary>
|
||||
public static partial class McpServerBuilderExtensions
|
||||
{
|
||||
private const string RequiresUnreferencedCodeMessage = "This method requires dynamic lookup of method metadata and might not work in Native AOT.";
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tool to the server.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTool">The tool type.</typeparam>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||
public static IMcpServerBuilder WithTool<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TTool>(this IMcpServerBuilder builder)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
List<AIFunction> functions = [];
|
||||
|
||||
PopulateFunctions(typeof(TTool), functions);
|
||||
return WithTools(builder, functions);
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds all tools marked with <see cref="McpToolTypeAttribute"/> from the current assembly to the server.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder)
|
||||
{
|
||||
return WithToolsFromAssembly(builder, Assembly.GetCallingAssembly());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds tools to the server.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
|
||||
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
Throw.IfNull(toolTypes);
|
||||
|
||||
List<AIFunction> functions = [];
|
||||
|
||||
foreach (var toolType in toolTypes)
|
||||
{
|
||||
if (toolType is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toolTypes), $"A tool type provided by the enumerator was null.");
|
||||
}
|
||||
|
||||
PopulateFunctions(toolType, functions);
|
||||
}
|
||||
|
||||
return WithTools(builder, functions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds tools to the server.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="functions"><see cref="AIFunction"/> instances to use as the tools.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="functions"/> is <see langword="null"/>.</exception>
|
||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<AIFunction> functions)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
Throw.IfNull(functions);
|
||||
|
||||
List<Tool> tools = [];
|
||||
Dictionary<string, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>> callbacks = [];
|
||||
|
||||
foreach (AIFunction function in functions)
|
||||
{
|
||||
if (function is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(functions), $"A function provided by the enumerator was null.");
|
||||
}
|
||||
|
||||
tools.Add(new()
|
||||
{
|
||||
Name = function.Name,
|
||||
Description = function.Description,
|
||||
InputSchema = JsonSerializer.Deserialize(function.JsonSchema, JsonSerializerOptionsExtensions.JsonContext.Default.JsonSchema),
|
||||
});
|
||||
|
||||
callbacks.Add(function.Name, async (request, cancellationToken) =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
object? result;
|
||||
try
|
||||
{
|
||||
result = await function.InvokeAsync((request.Params?.Arguments ?? [])!, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
return new CallToolResponse()
|
||||
{
|
||||
IsError = true,
|
||||
Content = [new() { Text = e.Message, Type = "text" }],
|
||||
};
|
||||
}
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case JsonElement je when je.ValueKind == JsonValueKind.Null:
|
||||
return new() { Content = [] };
|
||||
|
||||
case JsonElement je when je.ValueKind == JsonValueKind.Array:
|
||||
return new() { Content = je.EnumerateArray().Select(x => new Content() { Text = x.ToString(), Type = "text" }).ToList() };
|
||||
|
||||
default:
|
||||
return new() { Content = [new() { Text = result?.ToString(), Type = "text" }] };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
builder.WithListToolsHandler((_, _) => Task.FromResult(new ListToolsResult() { Tools = tools }));
|
||||
|
||||
builder.WithCallToolHandler(async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null || !callbacks.TryGetValue(request.Params.Name, out var callback))
|
||||
{
|
||||
throw new McpServerException($"Unknown tool '{request.Params?.Name}'");
|
||||
}
|
||||
|
||||
return await callback(request, cancellationToken).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds types marked with the <see cref="McpToolTypeAttribute"/> attribute from the given assembly as tools to the server.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
/// <param name="assembly">The assembly to load the types from. Null to get the current assembly</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||
[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
|
||||
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
|
||||
List<Type> toolTypes = [];
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.GetCustomAttribute<McpToolTypeAttribute>() is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
if (method.GetCustomAttribute<McpToolAttribute>() is not null)
|
||||
{
|
||||
toolTypes.Add(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toolTypes.Count > 0 ?
|
||||
WithTools(builder, toolTypes) :
|
||||
builder;
|
||||
}
|
||||
|
||||
private static void PopulateFunctions(
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type toolType,
|
||||
List<AIFunction> functions)
|
||||
{
|
||||
foreach (var method in toolType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
if (method.GetCustomAttribute<McpToolAttribute>() is not { } attribute)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
functions.Add(AIFunctionFactory.Create(method, target: null, new()
|
||||
{
|
||||
Name = attribute.Name ?? method.Name,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using ModelContextProtocol.Configuration;
|
||||
using ModelContextProtocol.Hosting;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Extension to configure the MCP server with transports
|
||||
/// </summary>
|
||||
public static partial class McpServerBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a server transport that uses stdin/stdout for communication.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
|
||||
{
|
||||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.AddSingleton<IServerTransport, StdioServerTransport>();
|
||||
builder.Services.AddHostedService<McpServerHostedService>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a server transport that uses SSE via a HttpListener for communication.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
public static IMcpServerBuilder WithHttpListenerSseServerTransport(this IMcpServerBuilder builder)
|
||||
{
|
||||
if (builder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
|
||||
builder.Services.AddHostedService<McpServerHostedService>();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
39
src/ModelContextProtocol/Configuration/McpServerConfig.cs
Normal file
39
src/ModelContextProtocol/Configuration/McpServerConfig.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
namespace ModelContextProtocol.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for an MCP server connection.
|
||||
/// This is passed to the client factory to create a client for a specific server.
|
||||
/// </summary>
|
||||
public record McpServerConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this server configuration.
|
||||
/// </summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name for the server.
|
||||
/// </summary>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of transport to use.
|
||||
/// </summary>
|
||||
public required string TransportType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// For stdio transport: path to the executable
|
||||
/// For HTTP transport: base URL of the server
|
||||
/// </summary>
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Arguments (if any) to pass to the executable.
|
||||
/// </summary>
|
||||
public string[]? Arguments { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional transport-specific configuration.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? TransportOptions { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ModelContextProtocol.Configuration;
|
||||
|
||||
internal sealed class McpServerOptionsSetup(IOptions<McpServerHandlers> serverHandlers) : IConfigureOptions<McpServerOptions>
|
||||
{
|
||||
public void Configure(McpServerOptions options)
|
||||
{
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var assemblyName = Assembly.GetEntryAssembly()?.GetName();
|
||||
options.ServerInfo = new Implementation
|
||||
{
|
||||
Name = assemblyName?.Name ?? "McpServer",
|
||||
Version = assemblyName?.Version?.ToString() ?? "1.0.0",
|
||||
};
|
||||
|
||||
serverHandlers.Value.OverwriteWithSetHandlers(options);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using ModelContextProtocol.Configuration;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
||||
/// <summary>
|
||||
/// Extension to host the MCP server
|
||||
/// </summary>
|
||||
public static class McpServerServiceCollectionExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the MCP server to the service collection with default options.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="configureOptions"></param>
|
||||
/// <returns></returns>
|
||||
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
|
||||
{
|
||||
services.AddSingleton(services =>
|
||||
{
|
||||
IServerTransport serverTransport = services.GetRequiredService<IServerTransport>();
|
||||
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
|
||||
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
|
||||
|
||||
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
|
||||
});
|
||||
|
||||
services.AddOptions();
|
||||
services.AddTransient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>();
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
}
|
||||
|
||||
return new DefaultMcpServerBuilder(services);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue