Improve tool handling (#28)
* Improve tool handling * Rework stuff with new hosting options * Update readme * Fix build * Try run client tests * Fix build * Fix issues on linux * Try to run testserver on linux * Fix test issue * Fix code smells
This commit is contained in:
parent
7d69499ad5
commit
ec9bdfcf5d
28 changed files with 1036 additions and 235 deletions
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
|
|
@ -3,10 +3,8 @@ name: .NET Build, Test and publish
|
|||
on:
|
||||
push:
|
||||
branches: [ "main", "master" ]
|
||||
paths: [ 'src/**' ]
|
||||
pull_request:
|
||||
branches: [ "main", "master" ]
|
||||
paths: [ 'src/**' ]
|
||||
release:
|
||||
types: [published,edited]
|
||||
|
||||
|
|
@ -24,7 +22,12 @@ jobs:
|
|||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: git fetch --prune --unshallow
|
||||
run: |
|
||||
if [ -f "$(git rev-parse --git-dir)/shallow" ]; then
|
||||
git fetch --prune --unshallow
|
||||
else
|
||||
echo "Repository is already complete."
|
||||
fi
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
|
|
@ -33,6 +36,17 @@ jobs:
|
|||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies for tests
|
||||
run: npm install @modelcontextprotocol/server-everything
|
||||
|
||||
- name: Install dependencies for tests
|
||||
run: npm install @modelcontextprotocol/server-memory
|
||||
|
||||
- name: Run Cake script
|
||||
uses: cake-build/cake-action@v1
|
||||
with:
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -62,4 +62,5 @@ ehthumbs.db
|
|||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
$RECYCLE.BIN/
|
||||
/mcpdotnet.v3.ncrunchsolution
|
||||
|
|
|
|||
29
README.MD
29
README.MD
|
|
@ -106,7 +106,33 @@ Remember you can connect to any MCP server, not just ones created using mcpdotne
|
|||
|
||||
## Getting Started (Server)
|
||||
|
||||
Here is an example of how to create an MCP server with a single tool. You can also refer to the TestServer in the tests folder, which implements a wider range of capabilities.
|
||||
Here is an example of how to create an MCP server and register all tools from the current application.
|
||||
|
||||
```csharp
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
builder.Services.AddMcpServer()
|
||||
.WithStdioServerTransport()
|
||||
.WithTools()
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
await app.RunAsync();
|
||||
```
|
||||
|
||||
The `EchoTool` class is a simple example of a tool implementation:
|
||||
```csharp
|
||||
[McpToolType]
|
||||
public static class EchoTool
|
||||
{
|
||||
[McpTool(Description = "Echoes the input back to the client.")]
|
||||
public static string Echo([McpParameter(true)] string message)
|
||||
{
|
||||
return "hello " + message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example of how to create an MCP server manually with a single tool. You can also refer to the TestServer in the tests folder, which implements a wider range of capabilities.
|
||||
|
||||
```csharp
|
||||
McpServerOptions options = new McpServerOptions()
|
||||
|
|
@ -171,6 +197,7 @@ while (true)
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Expand documentation with detailed guides for:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ try
|
|||
builder.Services.AddSerilog();
|
||||
builder.Services.AddMcpServer()
|
||||
.WithStdioServerTransport()
|
||||
.WithTools()
|
||||
.WithCallToolHandler((r, ct) => Task.FromResult(new McpDotNet.Protocol.Types.CallToolResponse()));
|
||||
|
||||
var app = builder.Build();
|
||||
|
|
|
|||
13
samples/TestServerWithHosting/Tools/EchoTool.cs
Normal file
13
samples/TestServerWithHosting/Tools/EchoTool.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using McpDotNet.Server;
|
||||
|
||||
namespace TestServerWithHosting.Tools;
|
||||
|
||||
[McpToolType]
|
||||
public static class EchoTool
|
||||
{
|
||||
[McpTool(Description = "Echoes the input back to the client.")]
|
||||
public static string Echo([McpParameter(true)] string message)
|
||||
{
|
||||
return "hello " + message;
|
||||
}
|
||||
}
|
||||
47
samples/TestServerWithHosting/Tools/SampleLlmTool.cs
Normal file
47
samples/TestServerWithHosting/Tools/SampleLlmTool.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
|
||||
namespace TestServerWithHosting.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// This tool uses depenency injection and async method
|
||||
/// </summary>
|
||||
[McpToolType]
|
||||
public class SampleLlmTool
|
||||
{
|
||||
private readonly IMcpServer _server;
|
||||
|
||||
public SampleLlmTool(IMcpServer server)
|
||||
{
|
||||
_server = server ?? throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
[McpTool(Name = "sampleLLM", Description = "Samples from an LLM using MCP's sampling feature")]
|
||||
public async Task<string> SampleLLM([McpParameter(true, Description = "The prompt to send to the LLM")] string prompt, [McpParameter(true, Description = "Maximum number of tokens to generate")] int maxTokens, CancellationToken cancellationToken)
|
||||
{
|
||||
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
|
||||
var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken);
|
||||
|
||||
return $"LLM sampling result: {sampleResult.Content.Text}";
|
||||
}
|
||||
|
||||
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
|
||||
{
|
||||
return new CreateMessageRequestParams()
|
||||
{
|
||||
Messages = [new SamplingMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"Resource {uri} context: {context}"
|
||||
}
|
||||
}],
|
||||
SystemPrompt = "You are a helpful test server.",
|
||||
MaxTokens = maxTokens,
|
||||
Temperature = 0.7f,
|
||||
IncludeContext = ContextInclusion.ThisServer
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace McpDotNet;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(s => s.ReadResourceHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for get resources 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)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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<string>, CancellationToken, Task> handler)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(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<string>, CancellationToken, Task> handler)
|
||||
{
|
||||
builder.Services.Configure<McpServerDelegates>(s => s.UnsubscribeFromResourcesHandler = handler);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
249
src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs
Normal file
249
src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace McpDotNet;
|
||||
|
||||
/// <summary>
|
||||
/// Extension to configure the MCP server with tools
|
||||
/// </summary>
|
||||
public static partial class McpServerBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a tool to the server.
|
||||
/// </summary>
|
||||
/// <typeparam name="TTool">The tool type.</typeparam>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
public static IMcpServerBuilder WithTool<TTool>(this IMcpServerBuilder builder)
|
||||
{
|
||||
return WithTools(builder, typeof(TTool));
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds all tools marked with <see cref="McpToolTypeAttribute"/> from the current assembly to the server.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder instance.</param>
|
||||
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>
|
||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params Type[] toolTypes)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(toolTypes);
|
||||
if (toolTypes.Length == 0)
|
||||
throw new ArgumentException("At least one tool type must be provided.", nameof(toolTypes));
|
||||
|
||||
var tools = new List<Tool>();
|
||||
Dictionary<string, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>> callbacks = [];
|
||||
|
||||
foreach (var type in toolTypes)
|
||||
{
|
||||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attribute = method.GetCustomAttribute<McpToolAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
var tool = CreateTool(method, attribute);
|
||||
tools.Add(tool);
|
||||
|
||||
callbacks.Add(tool.Name, async (request, cancellationToken) => await CallTool(request, method, cancellationToken));
|
||||
|
||||
// register type because method is not static and so we need an instance
|
||||
if (!method.IsStatic)
|
||||
builder.Services.AddScoped(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.WithListToolsHandler((_, _) => Task.FromResult(new ListToolsResult() { Tools = tools }));
|
||||
|
||||
builder.WithCallToolHandler(async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params != null && callbacks.TryGetValue(request.Params.Name, out var callback))
|
||||
return await callback(request, cancellationToken);
|
||||
|
||||
throw new McpServerException($"Unknown tool: {request.Params?.Name}");
|
||||
});
|
||||
|
||||
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>
|
||||
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? assembly = null)
|
||||
{
|
||||
assembly ??= Assembly.GetCallingAssembly();
|
||||
|
||||
List<Type> toolTypes = [];
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
bool hasToolTypeAttribute = type.GetCustomAttribute<McpToolTypeAttribute>() != null;
|
||||
if (!hasToolTypeAttribute)
|
||||
continue;
|
||||
|
||||
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attribute = method.GetCustomAttribute<McpToolAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
toolTypes.Add(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toolTypes.Count == 0)
|
||||
throw new ArgumentException("No types with marked methods found in the assembly.", nameof(assembly));
|
||||
|
||||
return WithTools(builder, toolTypes.ToArray());
|
||||
}
|
||||
|
||||
private static Tool CreateTool(MethodInfo method, McpToolAttribute attribute)
|
||||
{
|
||||
Dictionary<string, JsonSchemaProperty> properties = [];
|
||||
List<string>? requiredProperties = null;
|
||||
|
||||
foreach (var parameter in method.GetParameters())
|
||||
{
|
||||
if (parameter.ParameterType == typeof(CancellationToken))
|
||||
continue;
|
||||
|
||||
var parameterAttribute = parameter.GetCustomAttribute<McpParameterAttribute>();
|
||||
|
||||
properties.Add(parameter.Name ?? "NoName", new JsonSchemaProperty()
|
||||
{
|
||||
Type = GetParameterType(parameter.ParameterType),
|
||||
Description = parameterAttribute?.Description
|
||||
});
|
||||
|
||||
if (parameterAttribute?.Required == true)
|
||||
{
|
||||
requiredProperties ??= [];
|
||||
requiredProperties.Add(parameter.Name ?? "NoName");
|
||||
}
|
||||
}
|
||||
|
||||
return new Tool()
|
||||
{
|
||||
Name = attribute.Name ?? method.Name,
|
||||
Description = attribute.Description,
|
||||
InputSchema = new JsonSchema()
|
||||
{
|
||||
Type = "object",
|
||||
Properties = properties,
|
||||
Required = requiredProperties
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetParameterType(Type parameterType)
|
||||
{
|
||||
return parameterType switch
|
||||
{
|
||||
Type t when t == typeof(string) => "string",
|
||||
Type t when t == typeof(int) || t == typeof(double) || t == typeof(float) => "number",
|
||||
Type t when t == typeof(bool) => "boolean",
|
||||
Type t when t.IsArray => "array",
|
||||
Type t when t == typeof(DateTime) => "string",
|
||||
_ => "object"
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task<CallToolResponse> CallTool(RequestContext<CallToolRequestParams> request, MethodInfo method, CancellationToken cancellationToken)
|
||||
{
|
||||
var methodParameters = method.GetParameters();
|
||||
List<object?> parameters = ResolveParameters(request, methodParameters, cancellationToken);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return new CallToolResponse { Content = [new Content { Text = "Operation was cancelled" }] };
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = request.Server.ServiceProvider?.CreateScope();
|
||||
var objectInstance = CreateObjectInstance(method, scope?.ServiceProvider);
|
||||
var result = method.Invoke(objectInstance, parameters.ToArray());
|
||||
|
||||
|
||||
if (result is Task task)
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
var resultProperty = task.GetType().GetProperty("Result");
|
||||
result = resultProperty?.GetValue(task);
|
||||
}
|
||||
|
||||
if (result is string resultString)
|
||||
return new CallToolResponse { Content = [new Content() { Text = resultString, Type = "text" }] };
|
||||
|
||||
if (result is string[] resultStringArray)
|
||||
return new CallToolResponse { Content = resultStringArray.Select(s => new Content() { Text = s, Type = "text" }).ToList() };
|
||||
|
||||
if (result is null)
|
||||
return new CallToolResponse { Content = [new Content() { Text = "null" }] };
|
||||
|
||||
if (result is JsonElement jsonElement)
|
||||
return new CallToolResponse { Content = [new Content() { Text = jsonElement.GetRawText(), Type = "text" }] };
|
||||
|
||||
return new CallToolResponse { Content = [new Content() { Text = result.ToString(), Type = "text" }] };
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
throw new McpServerException(e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<object?> ResolveParameters(RequestContext<CallToolRequestParams> request, ParameterInfo[] methodParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
var parameters = new List<object?>(methodParameters.Length);
|
||||
|
||||
foreach (var parameter in methodParameters)
|
||||
{
|
||||
if (parameter.ParameterType == typeof(CancellationToken))
|
||||
{
|
||||
parameters.Add(cancellationToken);
|
||||
}
|
||||
else if (request.Params?.Arguments != null && request.Params.Arguments.TryGetValue(parameter.Name ?? "NoName", out var value))
|
||||
{
|
||||
if (value is JsonElement element)
|
||||
value = JsonSerializer.Deserialize(element.GetRawText(), parameter.ParameterType);
|
||||
|
||||
parameters.Add(Convert.ChangeType(value, parameter.ParameterType));
|
||||
}
|
||||
else
|
||||
{
|
||||
var parameterAttribute = parameter.GetCustomAttribute<McpParameterAttribute>();
|
||||
|
||||
if (parameterAttribute?.Required == true)
|
||||
throw new McpServerException($"Missing required argument '{parameter.Name}'.");
|
||||
|
||||
parameters.Add(parameter.HasDefaultValue ? parameter.DefaultValue : null);
|
||||
}
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static object? CreateObjectInstance(MethodInfo method, IServiceProvider? serviceProvider)
|
||||
{
|
||||
if (method.IsStatic)
|
||||
return null;
|
||||
|
||||
if (serviceProvider != null)
|
||||
return ActivatorUtilities.CreateInstance(serviceProvider, method.DeclaringType!);
|
||||
|
||||
return Activator.CreateInstance(method.DeclaringType!);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace McpDotNet;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
builder.Services.AddSingleton<IServerTransport, StdioServerTransport>();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,11 @@ public interface IMcpServer : IAsyncDisposable
|
|||
/// </summary>
|
||||
Implementation? ClientInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service provider for the server.
|
||||
/// </summary>
|
||||
IServiceProvider? ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts the server and begins listening for client requests.
|
||||
/// </summary>
|
||||
|
|
@ -31,47 +36,47 @@ public interface IMcpServer : IAsyncDisposable
|
|||
/// <summary>
|
||||
/// Gets or sets the handler for list tools requests.
|
||||
/// </summary>
|
||||
Func<ListToolsRequestParams?, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for call tool requests.
|
||||
/// </summary>
|
||||
Func<CallToolRequestParams?, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for list prompts requests.
|
||||
/// </summary>
|
||||
Func<ListPromptsRequestParams?, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for get prompt requests.
|
||||
/// </summary>
|
||||
Func<GetPromptRequestParams?, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for list resources requests.
|
||||
/// </summary>
|
||||
Func<ListResourcesRequestParams?, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for read resources requests.
|
||||
/// </summary>
|
||||
Func<ReadResourceRequestParams?, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for get resources requests.
|
||||
/// </summary>
|
||||
Func<CompleteRequestParams?, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
Func<string, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
Func<RequestContext<string>, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
Func<string, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
Func<RequestContext<string>, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
29
src/mcpdotnet/Server/McpParameterAttribute.cs
Normal file
29
src/mcpdotnet/Server/McpParameterAttribute.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
namespace McpDotNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to mark a method parameter
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public class McpParameterAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines if the parameter is required.
|
||||
/// </summary>
|
||||
public bool Required { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A description of the parameter.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to mark a method as an MCP tool.
|
||||
/// </summary>
|
||||
/// <param name="required">True if the parameter is mandatory.</param>
|
||||
/// <param name="description">A description of the tool.</param>
|
||||
public McpParameterAttribute(bool required = false, string? description = null)
|
||||
{
|
||||
Required = required;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,8 @@ namespace McpDotNet.Server;
|
|||
/// <inheritdoc />
|
||||
internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||
{
|
||||
private IServerTransport _serverTransport;
|
||||
private McpServerOptions _options;
|
||||
private readonly IServerTransport _serverTransport;
|
||||
private readonly McpServerOptions _options;
|
||||
private volatile bool _isInitializing;
|
||||
private readonly ILogger<McpServer> _logger;
|
||||
|
||||
|
|
@ -24,142 +24,24 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
/// <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(IServerTransport transport, McpServerOptions options, ILoggerFactory loggerFactory)
|
||||
public McpServer(IServerTransport transport, McpServerOptions options, ILoggerFactory loggerFactory, IServiceProvider? serviceProvider)
|
||||
: base(transport, loggerFactory)
|
||||
{
|
||||
_serverTransport = transport;
|
||||
_options = options;
|
||||
_logger = loggerFactory.CreateLogger<McpServer>();
|
||||
ServerInstructions = options.ServerInstructions;
|
||||
ServiceProvider = serviceProvider;
|
||||
|
||||
if (options.Capabilities?.Tools != null)
|
||||
{
|
||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>("tools/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListToolsHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListToolsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListTools handler not configured");
|
||||
}
|
||||
|
||||
return await ListToolsHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>("tools/call",
|
||||
async (request) =>
|
||||
{
|
||||
if (CallToolHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.CallToolHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("CallTool handler not configured");
|
||||
}
|
||||
|
||||
return await CallToolHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
if (options.Capabilities?.Prompts != null)
|
||||
{
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>("prompts/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListPromptsHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListPromptsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListPrompts handler not configured");
|
||||
}
|
||||
|
||||
return await ListPromptsHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetPromptHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.GetPromptHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("GetPrompt handler not configured");
|
||||
}
|
||||
|
||||
return await GetPromptHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
if (options.Capabilities?.Resources != null)
|
||||
{
|
||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>("resources/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListResourcesHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListResourcesHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListResources handler not configured");
|
||||
}
|
||||
|
||||
return await ListResourcesHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>("resources/read",
|
||||
async (request) =>
|
||||
{
|
||||
if (ReadResourceHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ReadResourceHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ReadResource handler not configured");
|
||||
}
|
||||
|
||||
return await ReadResourceHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
SetRequestHandler<CompleteRequestParams, CompleteResult>("completion/complete",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetCompletionHandler == null)
|
||||
{
|
||||
// This capability is not optional, so return an empty result if there is no handler
|
||||
return new CompleteResult()
|
||||
{
|
||||
Completion = new()
|
||||
{
|
||||
Values = [],
|
||||
Total = 0,
|
||||
HasMore = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return await GetCompletionHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
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(),
|
||||
});
|
||||
});
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
(request) =>
|
||||
{
|
||||
return Task.FromResult(new PingResult());
|
||||
});
|
||||
|
||||
OnNotification("notifications/initialized", (notification) =>
|
||||
{
|
||||
IsInitialized = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
SetToolsHandler(options);
|
||||
SetPromptsHandler(options);
|
||||
SetResourcesHandler(options);
|
||||
SetCompletionHandler();
|
||||
SetInitializeHandler(options);
|
||||
SetPingHandler();
|
||||
SetNotificationHandler();
|
||||
}
|
||||
|
||||
public ClientCapabilities? ClientCapabilities { get; set; }
|
||||
|
|
@ -171,31 +53,34 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
public string? ServerInstructions { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<ListToolsRequestParams?, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
public IServiceProvider? ServiceProvider { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<CallToolRequestParams?, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<ListPromptsRequestParams?, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
public Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<GetPromptRequestParams?, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<ListResourcesRequestParams?, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<ReadResourceRequestParams?, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<CompleteRequestParams?, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<string, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<string, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string EndpointName
|
||||
|
|
@ -277,4 +162,158 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SetNotificationHandler()
|
||||
{
|
||||
OnNotification("notifications/initialized", (notification) =>
|
||||
{
|
||||
IsInitialized = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
private void SetPingHandler()
|
||||
{
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
(request) =>
|
||||
{
|
||||
return 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()
|
||||
{
|
||||
SetRequestHandler<CompleteRequestParams, CompleteResult>("completion/complete",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetCompletionHandler == null)
|
||||
{
|
||||
// This capability is not optional, so return an empty result if there is no handler
|
||||
return new CompleteResult()
|
||||
{
|
||||
Completion = new()
|
||||
{
|
||||
Values = [],
|
||||
Total = 0,
|
||||
HasMore = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return await GetCompletionHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetResourcesHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Resources != null)
|
||||
{
|
||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>("resources/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListResourcesHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListResourcesHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListResources handler not configured");
|
||||
}
|
||||
|
||||
return await ListResourcesHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>("resources/read",
|
||||
async (request) =>
|
||||
{
|
||||
if (ReadResourceHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ReadResourceHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ReadResource handler not configured");
|
||||
}
|
||||
|
||||
return await ReadResourceHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPromptsHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Prompts != null)
|
||||
{
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>("prompts/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListPromptsHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListPromptsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListPrompts handler not configured");
|
||||
}
|
||||
|
||||
return await ListPromptsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetPromptHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.GetPromptHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("GetPrompt handler not configured");
|
||||
}
|
||||
|
||||
return await GetPromptHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetToolsHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Tools != null)
|
||||
{
|
||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>("tools/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListToolsHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListToolsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListTools handler not configured");
|
||||
}
|
||||
|
||||
return await ListToolsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
|
||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>("tools/call",
|
||||
async (request) =>
|
||||
{
|
||||
if (CallToolHandler == null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.CallToolHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("CallTool handler not configured");
|
||||
}
|
||||
|
||||
return await CallToolHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,47 +10,47 @@ public class McpServerDelegates
|
|||
/// <summary>
|
||||
/// Gets or sets the handler for list tools requests.
|
||||
/// </summary>
|
||||
public Func<ListToolsRequestParams?, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for call tool requests.
|
||||
/// </summary>
|
||||
public Func<CallToolRequestParams?, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
public Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for list prompts requests.
|
||||
/// </summary>
|
||||
public Func<ListPromptsRequestParams?, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for get prompt requests.
|
||||
/// </summary>
|
||||
public Func<GetPromptRequestParams?, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for list resources requests.
|
||||
/// </summary>
|
||||
public Func<ListResourcesRequestParams?, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for read resources requests.
|
||||
/// </summary>
|
||||
public Func<ReadResourceRequestParams?, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for get resources requests.
|
||||
/// </summary>
|
||||
public Func<CompleteRequestParams?, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
public Func<string, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for subscribe to resources messages.
|
||||
/// </summary>
|
||||
public Func<string, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Applies the delegates to the server.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public class McpServerFactory : IMcpServerFactory
|
|||
private readonly McpServerOptions _options;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly McpServerDelegates? _serverDelegates;
|
||||
private readonly IServiceProvider? _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpServerFactory"/> class.
|
||||
|
|
@ -27,14 +28,16 @@ public class McpServerFactory : IMcpServerFactory
|
|||
/// <param name="serverTransport">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="serviceProvider">Optional service provider to create new instances.</param>
|
||||
/// <param name="loggerFactory">Logger factory to use for logging</param>
|
||||
/// <param name="serverDelegates"></param>
|
||||
public McpServerFactory(IServerTransport serverTransport, McpServerOptions options, ILoggerFactory loggerFactory, IOptions<McpServerDelegates>? serverDelegates = null)
|
||||
public McpServerFactory(IServerTransport serverTransport, McpServerOptions options, ILoggerFactory loggerFactory, IOptions<McpServerDelegates>? serverDelegates = null, IServiceProvider? serviceProvider = null)
|
||||
{
|
||||
_serverTransport = serverTransport ?? throw new ArgumentNullException(nameof(serverTransport));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
_serverDelegates = serverDelegates?.Value;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -44,7 +47,7 @@ public class McpServerFactory : IMcpServerFactory
|
|||
/// </summary>
|
||||
public IMcpServer CreateServer()
|
||||
{
|
||||
var server = new McpServer(_serverTransport, _options, _loggerFactory);
|
||||
var server = new McpServer(_serverTransport, _options, _loggerFactory, _serviceProvider);
|
||||
|
||||
_serverDelegates?.Apply(server);
|
||||
|
||||
|
|
|
|||
27
src/mcpdotnet/Server/McpToolAttribute.cs
Normal file
27
src/mcpdotnet/Server/McpToolAttribute.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace McpDotNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to mark a method as an MCP tool.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class McpToolAttribute : Attribute
|
||||
{
|
||||
/// The name of the tool. If not provided, the class name will be used.
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A description of the tool.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <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>
|
||||
/// <param name="description">A description of the tool.</param>
|
||||
public McpToolAttribute(string? name = null, string? description = null)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
9
src/mcpdotnet/Server/McpToolTypeAttribute.cs
Normal file
9
src/mcpdotnet/Server/McpToolTypeAttribute.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace McpDotNet.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute to mark a type as container for MCP tools.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class McpToolTypeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
10
src/mcpdotnet/Server/RequestContext.cs
Normal file
10
src/mcpdotnet/Server/RequestContext.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace McpDotNet.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)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ internal abstract class McpJsonRpcEndpoint
|
|||
private readonly ITransport _transport;
|
||||
private readonly ConcurrentDictionary<RequestId, TaskCompletionSource<IJsonRpcMessage>> _pendingRequests;
|
||||
private readonly ConcurrentDictionary<string, List<Func<JsonRpcNotification, Task>>> _notificationHandlers;
|
||||
private readonly Dictionary<string, Func<JsonRpcRequest, Task<object>>> _requestHandlers = [];
|
||||
private readonly Dictionary<string, Func<JsonRpcRequest, Task<object?>>> _requestHandlers = [];
|
||||
private int _nextRequestId;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly ILogger<McpClient> _logger;
|
||||
|
|
@ -349,7 +349,7 @@ internal abstract class McpJsonRpcEndpoint
|
|||
/// <typeparam name="TResponse">Type of response payload (not full RPC response</typeparam>
|
||||
/// <param name="method">Method identifier to register for</param>
|
||||
/// <param name="handler">Handler to be called when a request with specified method identifier is received</param>
|
||||
protected void SetRequestHandler<TRequest, TResponse>(string method, Func<TRequest?, Task<TResponse>> handler) where TResponse : class
|
||||
protected void SetRequestHandler<TRequest, TResponse>(string method, Func<TRequest?, Task<TResponse>> handler)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(handler);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class IntegrationTests
|
|||
{
|
||||
Id = "everything",
|
||||
Name = "Everything",
|
||||
TransportType = McpDotNet.Protocol.Transport.TransportTypes.StdIo,
|
||||
TransportType = "stdio",
|
||||
TransportOptions = new Dictionary<string, string>
|
||||
{
|
||||
["command"] = "npx",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\McpDotNet.Extensions.AI\McpDotNet.Extensions.AI.csproj" />
|
||||
<ProjectReference Include="..\..\src\mcpdotnet\mcpdotnet.csproj" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.3.0-preview.1.25114.11" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue