Add McpServerPrompt / McpClientPrompt and friends (#126)
This commit is contained in:
parent
2c75acded8
commit
5d3fb65b11
50 changed files with 1950 additions and 550 deletions
37
README.md
37
README.md
|
|
@ -84,9 +84,9 @@ the employed overload of `WithTools` examines the current assembly for classes w
|
||||||
`McpTool` attribute as tools.)
|
`McpTool` attribute as tools.)
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using ModelContextProtocol;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Server;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
||||||
|
|
@ -109,7 +109,7 @@ the connected client. Similarly, arguments may be injected via dependency inject
|
||||||
`IMcpServer` to make sampling requests back to the client in order to summarize content it downloads from the specified url via
|
`IMcpServer` to make sampling requests back to the client in order to summarize content it downloads from the specified url via
|
||||||
an `HttpClient` injected via dependency injection.
|
an `HttpClient` injected via dependency injection.
|
||||||
```csharp
|
```csharp
|
||||||
[McpServerTool("SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")]
|
[McpServerTool(Name = "SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")]
|
||||||
public static async Task<string> SummarizeDownloadedContent(
|
public static async Task<string> SummarizeDownloadedContent(
|
||||||
IMcpServer thisServer,
|
IMcpServer thisServer,
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
|
|
@ -122,8 +122,8 @@ public static async Task<string> SummarizeDownloadedContent(
|
||||||
[
|
[
|
||||||
new(ChatRole.User, "Briefly summarize the following downloaded content:"),
|
new(ChatRole.User, "Briefly summarize the following downloaded content:"),
|
||||||
new(ChatRole.User, content),
|
new(ChatRole.User, content),
|
||||||
]
|
];
|
||||||
|
|
||||||
ChatOptions options = new()
|
ChatOptions options = new()
|
||||||
{
|
{
|
||||||
MaxOutputTokens = 256,
|
MaxOutputTokens = 256,
|
||||||
|
|
@ -134,13 +134,24 @@ public static async Task<string> SummarizeDownloadedContent(
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Prompts can be exposed in a similar manner, using `[McpServerPrompt]`, e.g.
|
||||||
|
```csharp
|
||||||
|
[McpServerPromptType]
|
||||||
|
public static class MyPrompts
|
||||||
|
{
|
||||||
|
[McpServerPrompt, Description("Creates a prompt to summarize the provided message.")]
|
||||||
|
public static ChatMessage Summarize([Description("The content to summarize")] string content) =>
|
||||||
|
new(ChatRole.User, $"Please summarize this content into a single sentence: {content}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
More control is also available, with fine-grained control over configuring the server and how it should handle client requests. For example:
|
More control is also available, with fine-grained control over configuring the server and how it should handle client requests. For example:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using System.Text.Json;
|
||||||
|
|
||||||
McpServerOptions options = new()
|
McpServerOptions options = new()
|
||||||
{
|
{
|
||||||
|
|
@ -149,9 +160,8 @@ McpServerOptions options = new()
|
||||||
{
|
{
|
||||||
Tools = new()
|
Tools = new()
|
||||||
{
|
{
|
||||||
ListToolsHandler = async (request, cancellationToken) =>
|
ListToolsHandler = (request, cancellationToken) =>
|
||||||
{
|
Task.FromResult(new ListToolsResult()
|
||||||
return new ListToolsResult()
|
|
||||||
{
|
{
|
||||||
Tools =
|
Tools =
|
||||||
[
|
[
|
||||||
|
|
@ -173,10 +183,9 @@ McpServerOptions options = new()
|
||||||
"""),
|
"""),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
}),
|
||||||
},
|
|
||||||
|
|
||||||
CallToolHandler = async (request, cancellationToken) =>
|
CallToolHandler = (request, cancellationToken) =>
|
||||||
{
|
{
|
||||||
if (request.Params?.Name == "echo")
|
if (request.Params?.Name == "echo")
|
||||||
{
|
{
|
||||||
|
|
@ -185,10 +194,10 @@ McpServerOptions options = new()
|
||||||
throw new McpServerException("Missing required argument 'message'");
|
throw new McpServerException("Missing required argument 'message'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CallToolResponse()
|
return Task.FromResult(new CallToolResponse()
|
||||||
{
|
{
|
||||||
Content = [new Content() { Text = $"Echo: {message}", Type = "text" }]
|
Content = [new Content() { Text = $"Echo: {message}", Type = "text" }]
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new McpServerException($"Unknown tool: '{request.Params?.Name}'");
|
throw new McpServerException($"Unknown tool: '{request.Params?.Name}'");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using ModelContextProtocol;
|
|
||||||
using AspNetCoreSseServer;
|
using AspNetCoreSseServer;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using ModelContextProtocol;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using ModelContextProtocol;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,37 @@ public static class AIContentExtensions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates <see cref="ChatMessage"/>s from a <see cref="GetPromptResult"/>.</summary>
|
||||||
|
/// <param name="promptResult">The messages to convert.</param>
|
||||||
|
/// <returns>The created <see cref="ChatMessage"/>.</returns>
|
||||||
|
public static IList<ChatMessage> ToChatMessages(this GetPromptResult promptResult)
|
||||||
|
{
|
||||||
|
Throw.IfNull(promptResult);
|
||||||
|
|
||||||
|
return promptResult.Messages.Select(m => m.ToChatMessage()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets <see cref="PromptMessage"/> instances for the specified <see cref="ChatMessage"/>.</summary>
|
||||||
|
/// <param name="chatMessage">The message for which to extract its contents as <see cref="PromptMessage"/> instances.</param>
|
||||||
|
/// <returns>The converted content.</returns>
|
||||||
|
public static IList<PromptMessage> ToPromptMessages(this ChatMessage chatMessage)
|
||||||
|
{
|
||||||
|
Throw.IfNull(chatMessage);
|
||||||
|
|
||||||
|
Role r = chatMessage.Role == ChatRole.User ? Role.User : Role.Assistant;
|
||||||
|
|
||||||
|
List<PromptMessage> messages = [];
|
||||||
|
foreach (var content in chatMessage.Contents)
|
||||||
|
{
|
||||||
|
if (content is TextContent or DataContent)
|
||||||
|
{
|
||||||
|
messages.Add(new PromptMessage { Role = r, Content = content.ToContent() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="Content"/>.</summary>
|
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="Content"/>.</summary>
|
||||||
/// <param name="content">The <see cref="Content"/> to convert.</param>
|
/// <param name="content">The <see cref="Content"/> to convert.</param>
|
||||||
/// <returns>The created <see cref="AIContent"/>.</returns>
|
/// <returns>The created <see cref="AIContent"/>.</returns>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ModelContextProtocol.Configuration;
|
|
||||||
using ModelContextProtocol.Logging;
|
using ModelContextProtocol.Logging;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
|
|
|
||||||
|
|
@ -115,13 +115,12 @@ public static class McpClientExtensions
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A list of all available prompts.</returns>
|
/// <returns>A list of all available prompts.</returns>
|
||||||
public static async Task<IList<Prompt>> ListPromptsAsync(
|
public static async Task<IList<McpClientPrompt>> ListPromptsAsync(
|
||||||
this IMcpClient client, CancellationToken cancellationToken = default)
|
this IMcpClient client, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
List<Prompt>? prompts = null;
|
List<McpClientPrompt>? prompts = null;
|
||||||
|
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
@ -129,13 +128,10 @@ public static class McpClientExtensions
|
||||||
CreateRequest(RequestMethods.PromptsList, CreateCursorDictionary(cursor)),
|
CreateRequest(RequestMethods.PromptsList, CreateCursorDictionary(cursor)),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (prompts is null)
|
prompts ??= new List<McpClientPrompt>(promptResults.Prompts.Count);
|
||||||
|
foreach (var prompt in promptResults.Prompts)
|
||||||
{
|
{
|
||||||
prompts = promptResults.Prompts;
|
prompts.Add(new McpClientPrompt(client, prompt));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prompts.AddRange(promptResults.Prompts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = promptResults.NextCursor;
|
cursor = promptResults.NextCursor;
|
||||||
|
|
@ -186,7 +182,7 @@ public static class McpClientExtensions
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the prompt's content and messages.</returns>
|
/// <returns>A task containing the prompt's content and messages.</returns>
|
||||||
public static Task<GetPromptResult> GetPromptAsync(
|
public static Task<GetPromptResult> GetPromptAsync(
|
||||||
this IMcpClient client, string name, Dictionary<string, object?>? arguments = null, CancellationToken cancellationToken = default)
|
this IMcpClient client, string name, IReadOnlyDictionary<string, object?>? arguments = null, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNullOrWhiteSpace(name);
|
Throw.IfNullOrWhiteSpace(name);
|
||||||
|
|
@ -345,7 +341,7 @@ public static class McpClientExtensions
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<ReadResourceResult>(
|
return client.SendRequestAsync<ReadResourceResult>(
|
||||||
CreateRequest(RequestMethods.ResourcesRead, new() { ["uri"] = uri }),
|
CreateRequest(RequestMethods.ResourcesRead, new Dictionary<string, object?>() { ["uri"] = uri }),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,7 +365,7 @@ public static class McpClientExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.SendRequestAsync<CompleteResult>(
|
return client.SendRequestAsync<CompleteResult>(
|
||||||
CreateRequest(RequestMethods.CompletionComplete, new()
|
CreateRequest(RequestMethods.CompletionComplete, new Dictionary<string, object?>()
|
||||||
{
|
{
|
||||||
["ref"] = reference,
|
["ref"] = reference,
|
||||||
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
|
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
|
||||||
|
|
@ -389,7 +385,7 @@ public static class McpClientExtensions
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync<EmptyResult>(
|
||||||
CreateRequest(RequestMethods.ResourcesSubscribe, new() { ["uri"] = uri }),
|
CreateRequest(RequestMethods.ResourcesSubscribe, new Dictionary<string, object?>() { ["uri"] = uri }),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,7 +401,7 @@ public static class McpClientExtensions
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync<EmptyResult>(
|
||||||
CreateRequest(RequestMethods.ResourcesUnsubscribe, new() { ["uri"] = uri }),
|
CreateRequest(RequestMethods.ResourcesUnsubscribe, new Dictionary<string, object?>() { ["uri"] = uri }),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -560,11 +556,11 @@ public static class McpClientExtensions
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync<EmptyResult>(
|
||||||
CreateRequest(RequestMethods.LoggingSetLevel, new() { ["level"] = level }),
|
CreateRequest(RequestMethods.LoggingSetLevel, new Dictionary<string, object?>() { ["level"] = level }),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonRpcRequest CreateRequest(string method, Dictionary<string, object?>? parameters) =>
|
private static JsonRpcRequest CreateRequest(string method, IReadOnlyDictionary<string, object?>? parameters) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Method = method,
|
Method = method,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using ModelContextProtocol.Configuration;
|
|
||||||
using ModelContextProtocol.Logging;
|
using ModelContextProtocol.Logging;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
|
|
|
||||||
41
src/ModelContextProtocol/Client/McpClientPrompt.cs
Normal file
41
src/ModelContextProtocol/Client/McpClientPrompt.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Client;
|
||||||
|
|
||||||
|
/// <summary>Provides an invocable prompt.</summary>
|
||||||
|
public sealed class McpClientPrompt
|
||||||
|
{
|
||||||
|
private readonly IMcpClient _client;
|
||||||
|
|
||||||
|
internal McpClientPrompt(IMcpClient client, Prompt prompt)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
ProtocolPrompt = prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the protocol <see cref="Prompt"/> type for this instance.</summary>
|
||||||
|
public Prompt ProtocolPrompt { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a specific prompt with optional arguments.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="arguments">Optional arguments for the prompt</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
/// <returns>A task containing the prompt's content and messages.</returns>
|
||||||
|
public async ValueTask<GetPromptResult> GetAsync(
|
||||||
|
IEnumerable<KeyValuePair<string, object?>>? arguments = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
IReadOnlyDictionary<string, object?>? argDict =
|
||||||
|
arguments as IReadOnlyDictionary<string, object?> ??
|
||||||
|
arguments?.ToDictionary();
|
||||||
|
|
||||||
|
return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets the name of the prompt.</summary>
|
||||||
|
public string Name => ProtocolPrompt.Name;
|
||||||
|
|
||||||
|
/// <summary>Gets a description of the prompt.</summary>
|
||||||
|
public string? Description => ProtocolPrompt.Description;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace ModelContextProtocol.Configuration;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default implementation of <see cref="IMcpServerBuilder"/>.
|
/// Default implementation of <see cref="IMcpServerBuilder"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class DefaultMcpServerBuilder : IMcpServerBuilder
|
internal sealed class DefaultMcpServerBuilder : IMcpServerBuilder
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IServiceCollection Services { get; }
|
public IServiceCollection Services { get; }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace ModelContextProtocol.Configuration;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builder for configuring <see cref="IMcpServer"/> instances.
|
/// Builder for configuring <see cref="IMcpServer"/> instances.
|
||||||
|
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
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 resource templates requests.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">The builder instance.</param>
|
|
||||||
/// <param name="handler">The handler.</param>
|
|
||||||
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.Configure<McpServerHandlers>(s => s.ListResourceTemplatesHandler = handler);
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
using ModelContextProtocol.Configuration;
|
|
||||||
using ModelContextProtocol.Server;
|
|
||||||
using ModelContextProtocol.Utils;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace ModelContextProtocol;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extension to configure the MCP server with tools
|
|
||||||
/// </summary>
|
|
||||||
public static partial class McpServerBuilderExtensions
|
|
||||||
{
|
|
||||||
private const string WithToolsRequiresUnreferencedCodeMessage =
|
|
||||||
$"The non-generic {nameof(WithTools)} and {nameof(WithToolsFromAssembly)} methods require dynamic lookup of method metadata" +
|
|
||||||
$"and may not work in Native AOT. Use the generic {nameof(WithTools)} method instead.";
|
|
||||||
|
|
||||||
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</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>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TTool"/>
|
|
||||||
/// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
|
|
||||||
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
|
||||||
/// </remarks>
|
|
||||||
public static IMcpServerBuilder WithTools<[DynamicallyAccessedMembers(
|
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods |
|
|
||||||
DynamicallyAccessedMemberTypes.NonPublicMethods |
|
|
||||||
DynamicallyAccessedMemberTypes.PublicConstructors)] TTool>(
|
|
||||||
this IMcpServerBuilder builder)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
foreach (var toolMethod in typeof(TTool).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
|
||||||
{
|
|
||||||
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
|
||||||
{
|
|
||||||
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
|
||||||
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
|
||||||
services => McpServerTool.Create(toolMethod, typeof(TTool), new() { Services = services })));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</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>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="toolTypes"/>
|
|
||||||
/// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
|
|
||||||
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
|
||||||
/// </remarks>
|
|
||||||
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
|
||||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
Throw.IfNull(toolTypes);
|
|
||||||
|
|
||||||
foreach (var toolType in toolTypes)
|
|
||||||
{
|
|
||||||
if (toolType is not null)
|
|
||||||
{
|
|
||||||
foreach (var toolMethod in toolType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
|
||||||
{
|
|
||||||
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
|
||||||
{
|
|
||||||
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
|
||||||
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
|
||||||
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">The builder instance.</param>
|
|
||||||
/// <param name="toolAssembly">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(WithToolsRequiresUnreferencedCodeMessage)]
|
|
||||||
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
toolAssembly ??= Assembly.GetCallingAssembly();
|
|
||||||
|
|
||||||
return builder.WithTools(
|
|
||||||
from t in toolAssembly.GetTypes()
|
|
||||||
where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
|
|
||||||
select t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
using ModelContextProtocol.Configuration;
|
|
||||||
using ModelContextProtocol.Hosting;
|
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
|
||||||
using ModelContextProtocol.Utils;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using ModelContextProtocol.Server;
|
|
||||||
|
|
||||||
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<ITransport, StdioServerTransport>();
|
|
||||||
builder.Services.AddHostedService<McpServerSingleSessionHostedService>();
|
|
||||||
|
|
||||||
builder.Services.AddSingleton(services =>
|
|
||||||
{
|
|
||||||
ITransport serverTransport = services.GetRequiredService<ITransport>();
|
|
||||||
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
|
|
||||||
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
|
|
||||||
|
|
||||||
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Throw.IfNull(builder);
|
|
||||||
|
|
||||||
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
|
|
||||||
builder.Services.AddHostedService<McpServerMultiSessionHostedService>();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using ModelContextProtocol.Hosting;
|
||||||
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using ModelContextProtocol.Utils;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods for configuring MCP servers via dependency injection.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class McpServerBuilderExtensions
|
||||||
|
{
|
||||||
|
#region WithTools
|
||||||
|
private const string WithToolsRequiresUnreferencedCodeMessage =
|
||||||
|
$"The non-generic {nameof(WithTools)} and {nameof(WithToolsFromAssembly)} methods require dynamic lookup of method metadata" +
|
||||||
|
$"and may not work in Native AOT. Use the generic {nameof(WithTools)} method instead.";
|
||||||
|
|
||||||
|
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
||||||
|
/// <typeparam name="TToolType">The tool type.</typeparam>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TToolType"/>
|
||||||
|
/// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
|
||||||
|
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
||||||
|
/// </remarks>
|
||||||
|
public static IMcpServerBuilder WithTools<[DynamicallyAccessedMembers(
|
||||||
|
DynamicallyAccessedMemberTypes.PublicMethods |
|
||||||
|
DynamicallyAccessedMemberTypes.NonPublicMethods |
|
||||||
|
DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
|
||||||
|
this IMcpServerBuilder builder)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
foreach (var toolMethod in typeof(TToolType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
||||||
|
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
||||||
|
services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</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>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="toolTypes"/>
|
||||||
|
/// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
|
||||||
|
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
||||||
|
/// </remarks>
|
||||||
|
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
||||||
|
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(toolTypes);
|
||||||
|
|
||||||
|
foreach (var toolType in toolTypes)
|
||||||
|
{
|
||||||
|
if (toolType is not null)
|
||||||
|
{
|
||||||
|
foreach (var toolMethod in toolType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
||||||
|
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
||||||
|
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="toolAssembly">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(WithToolsRequiresUnreferencedCodeMessage)]
|
||||||
|
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
toolAssembly ??= Assembly.GetCallingAssembly();
|
||||||
|
|
||||||
|
return builder.WithTools(
|
||||||
|
from t in toolAssembly.GetTypes()
|
||||||
|
where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
|
||||||
|
select t);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region WithPrompts
|
||||||
|
private const string WithPromptsRequiresUnreferencedCodeMessage =
|
||||||
|
$"The non-generic {nameof(WithPrompts)} and {nameof(WithPromptsFromAssembly)} methods require dynamic lookup of method metadata" +
|
||||||
|
$"and may not work in Native AOT. Use the generic {nameof(WithPrompts)} method instead.";
|
||||||
|
|
||||||
|
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
||||||
|
/// <typeparam name="TPromptType">The prompt type.</typeparam>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TPromptType"/>
|
||||||
|
/// type, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
|
||||||
|
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
|
||||||
|
/// </remarks>
|
||||||
|
public static IMcpServerBuilder WithPrompts<[DynamicallyAccessedMembers(
|
||||||
|
DynamicallyAccessedMemberTypes.PublicMethods |
|
||||||
|
DynamicallyAccessedMemberTypes.NonPublicMethods |
|
||||||
|
DynamicallyAccessedMemberTypes.PublicConstructors)] TPromptType>(
|
||||||
|
this IMcpServerBuilder builder)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
foreach (var promptMethod in typeof(TPromptType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (promptMethod.GetCustomAttribute<McpServerPromptAttribute>() is not null)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
|
||||||
|
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
|
||||||
|
services => McpServerPrompt.Create(promptMethod, typeof(TPromptType), new() { Services = services })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="promptTypes">Types with marked methods to add as prompts to the server.</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="promptTypes"/> is <see langword="null"/>.</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="promptTypes"/>
|
||||||
|
/// types, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
|
||||||
|
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
|
||||||
|
/// </remarks>
|
||||||
|
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
|
||||||
|
public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, params IEnumerable<Type> promptTypes)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
Throw.IfNull(promptTypes);
|
||||||
|
|
||||||
|
foreach (var promptType in promptTypes)
|
||||||
|
{
|
||||||
|
if (promptType is not null)
|
||||||
|
{
|
||||||
|
foreach (var promptMethod in promptType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (promptMethod.GetCustomAttribute<McpServerPromptAttribute>() is not null)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
|
||||||
|
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
|
||||||
|
services => McpServerPrompt.Create(promptMethod, promptType, new() { Services = services })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as prompts to the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="promptAssembly">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(WithPromptsRequiresUnreferencedCodeMessage)]
|
||||||
|
public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
promptAssembly ??= Assembly.GetCallingAssembly();
|
||||||
|
|
||||||
|
return builder.WithPrompts(
|
||||||
|
from t in promptAssembly.GetTypes()
|
||||||
|
where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
|
||||||
|
select t);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Handlers
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the handler for list resource templates requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="handler">The handler.</param>
|
||||||
|
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
builder.Services.Configure<McpServerHandlers>(s => s.ListResourceTemplatesHandler = handler);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Transports
|
||||||
|
/// <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<ITransport, StdioServerTransport>();
|
||||||
|
builder.Services.AddHostedService<McpServerSingleSessionHostedService>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(services =>
|
||||||
|
{
|
||||||
|
ITransport serverTransport = services.GetRequiredService<ITransport>();
|
||||||
|
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
|
||||||
|
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
|
||||||
|
|
||||||
|
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
|
||||||
|
});
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
|
||||||
|
builder.Services.AddHostedService<McpServerMultiSessionHostedService>();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace ModelContextProtocol.Configuration;
|
namespace ModelContextProtocol;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration for an MCP server connection.
|
/// Configuration for an MCP server connection.
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@ using ModelContextProtocol.Server;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Configuration;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the McpServerOptions using addition services from DI.
|
/// Configures the McpServerOptions using addition services from DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serverHandlers">The server handlers configuration options.</param>
|
/// <param name="serverHandlers">The server handlers configuration options.</param>
|
||||||
/// <param name="serverTools">Tools individually registered.</param>
|
/// <param name="serverTools">Tools individually registered.</param>
|
||||||
|
/// <param name="serverPrompts">Prompts individually registered.</param>
|
||||||
internal sealed class McpServerOptionsSetup(
|
internal sealed class McpServerOptionsSetup(
|
||||||
IOptions<McpServerHandlers> serverHandlers,
|
IOptions<McpServerHandlers> serverHandlers,
|
||||||
IEnumerable<McpServerTool> serverTools) : IConfigureOptions<McpServerOptions>
|
IEnumerable<McpServerTool> serverTools,
|
||||||
|
IEnumerable<McpServerPrompt> serverPrompts) : IConfigureOptions<McpServerOptions>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the given McpServerOptions instance by setting server information
|
/// Configures the given McpServerOptions instance by setting server information
|
||||||
|
|
@ -39,17 +41,34 @@ internal sealed class McpServerOptionsSetup(
|
||||||
// a collection, add to it, otherwise create a new one. We want to maintain the identity
|
// a collection, add to it, otherwise create a new one. We want to maintain the identity
|
||||||
// of an existing collection in case someone has provided their own derived type, wants
|
// of an existing collection in case someone has provided their own derived type, wants
|
||||||
// change notifications, etc.
|
// change notifications, etc.
|
||||||
McpServerToolCollection toolsCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
|
McpServerPrimitiveCollection<McpServerTool> toolCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
|
||||||
foreach (var tool in serverTools)
|
foreach (var tool in serverTools)
|
||||||
{
|
{
|
||||||
toolsCollection.TryAdd(tool);
|
toolCollection.TryAdd(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toolsCollection.IsEmpty)
|
if (!toolCollection.IsEmpty)
|
||||||
{
|
{
|
||||||
options.Capabilities ??= new();
|
options.Capabilities ??= new();
|
||||||
options.Capabilities.Tools ??= new();
|
options.Capabilities.Tools ??= new();
|
||||||
options.Capabilities.Tools.ToolCollection = toolsCollection;
|
options.Capabilities.Tools.ToolCollection = toolCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all of the provided prompts into a prompts collection. If the options already has
|
||||||
|
// a collection, add to it, otherwise create a new one. We want to maintain the identity
|
||||||
|
// of an existing collection in case someone has provided their own derived type, wants
|
||||||
|
// change notifications, etc.
|
||||||
|
McpServerPrimitiveCollection<McpServerPrompt> promptCollection = options.Capabilities?.Prompts?.PromptCollection ?? [];
|
||||||
|
foreach (var prompt in serverPrompts)
|
||||||
|
{
|
||||||
|
promptCollection.TryAdd(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!promptCollection.IsEmpty)
|
||||||
|
{
|
||||||
|
options.Capabilities ??= new();
|
||||||
|
options.Capabilities.Prompts ??= new();
|
||||||
|
options.Capabilities.Prompts.PromptCollection = promptCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply custom server handlers.
|
// Apply custom server handlers.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension to host an MCP server
|
||||||
|
/// </summary>
|
||||||
|
public static class McpServerServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <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.AddOptions();
|
||||||
|
services.AddTransient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>();
|
||||||
|
if (configureOptions is not null)
|
||||||
|
{
|
||||||
|
services.Configure(configureOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultMcpServerBuilder(services);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ public static class NotificationMethods
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sent by the server when the list of prompts changes.
|
/// Sent by the server when the list of prompts changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string PromptsListChanged = "notifications/prompts/list_changed";
|
public const string PromptListChangedNotification = "notifications/prompts/list_changed";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sent by the server when the list of resources changes.
|
/// Sent by the server when the list of resources changes.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using ModelContextProtocol.Configuration;
|
|
||||||
using ModelContextProtocol.Logging;
|
using ModelContextProtocol.Logging;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue