Add McpServerPrompt / McpClientPrompt and friends (#126)
This commit is contained in:
parent
2c75acded8
commit
5d3fb65b11
13 changed files with 1950 additions and 550 deletions
256
src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs
Normal file
256
src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Provides an <see cref="McpServerPrompt"/> that's implemented via an <see cref="AIFunction"/>.</summary>
|
||||
internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||
{
|
||||
/// <summary>Key used temporarily for flowing request context into an AIFunction.</summary>
|
||||
/// <remarks>This will be replaced with use of AIFunctionArguments.Context.</remarks>
|
||||
internal const string RequestContextKey = "__temporary_RequestContext";
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
|
||||
/// </summary>
|
||||
public static new AIFunctionMcpServerPrompt Create(
|
||||
Delegate method,
|
||||
McpServerPromptCreateOptions? options)
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
|
||||
options = DeriveOptions(method.Method, options);
|
||||
|
||||
return Create(method.Method, method.Target, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
|
||||
/// </summary>
|
||||
public static new AIFunctionMcpServerPrompt Create(
|
||||
MethodInfo method,
|
||||
object? target,
|
||||
McpServerPromptCreateOptions? options)
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
|
||||
// TODO: Once this repo consumes a new build of Microsoft.Extensions.AI containing
|
||||
// https://github.com/dotnet/extensions/pull/6158,
|
||||
// https://github.com/dotnet/extensions/pull/6162, and
|
||||
// https://github.com/dotnet/extensions/pull/6175, switch over to using the real
|
||||
// AIFunctionFactory, delete the TemporaryXx types, and fix-up the mechanism by
|
||||
// which the arguments are passed.
|
||||
|
||||
options = DeriveOptions(method, options);
|
||||
|
||||
return Create(
|
||||
TemporaryAIFunctionFactory.Create(method, target, CreateAIFunctionFactoryOptions(method, options)),
|
||||
options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
|
||||
/// </summary>
|
||||
public static new AIFunctionMcpServerPrompt Create(
|
||||
MethodInfo method,
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType,
|
||||
McpServerPromptCreateOptions? options)
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
|
||||
options = DeriveOptions(method, options);
|
||||
|
||||
return Create(
|
||||
TemporaryAIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)),
|
||||
options);
|
||||
}
|
||||
|
||||
private static TemporaryAIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
|
||||
MethodInfo method, McpServerPromptCreateOptions? options) =>
|
||||
new()
|
||||
{
|
||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerPromptAttribute>()?.Name,
|
||||
Description = options?.Description,
|
||||
MarshalResult = static (result, _, cancellationToken) => Task.FromResult(result),
|
||||
ConfigureParameterBinding = pi =>
|
||||
{
|
||||
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) => GetRequestContext(args),
|
||||
};
|
||||
}
|
||||
|
||||
if (pi.ParameterType == typeof(IMcpServer))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) => GetRequestContext(args)?.Server,
|
||||
};
|
||||
}
|
||||
|
||||
// We assume that if the services used to create the prompt support a particular type,
|
||||
// so too do the services associated with the server. This is the same basic assumption
|
||||
// made in ASP.NET.
|
||||
if (options?.Services is { } services &&
|
||||
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||
ispis.IsService(pi.ParameterType))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
GetRequestContext(args)?.Server?.Services?.GetService(pi.ParameterType) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
(GetRequestContext(args)?.Server?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
static RequestContext<GetPromptRequestParams>? GetRequestContext(IReadOnlyDictionary<string, object?> args)
|
||||
{
|
||||
if (args.TryGetValue(RequestContextKey, out var orc) &&
|
||||
orc is RequestContext<GetPromptRequestParams> requestContext)
|
||||
{
|
||||
return requestContext;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
||||
public static new AIFunctionMcpServerPrompt Create(AIFunction function, McpServerPromptCreateOptions? options)
|
||||
{
|
||||
Throw.IfNull(function);
|
||||
|
||||
List<PromptArgument> args = [];
|
||||
if (function.JsonSchema.TryGetProperty("properties", out JsonElement properties))
|
||||
{
|
||||
foreach (var param in properties.EnumerateObject())
|
||||
{
|
||||
args.Add(new()
|
||||
{
|
||||
Name = param.Name,
|
||||
Description = param.Value.TryGetProperty("description", out JsonElement description) ? description.GetString() : null,
|
||||
Required = param.Value.TryGetProperty("required", out JsonElement required) && required.GetBoolean(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Prompt prompt = new()
|
||||
{
|
||||
Name = options?.Name ?? function.Name,
|
||||
Description = options?.Description ?? function.Description,
|
||||
Arguments = args,
|
||||
};
|
||||
|
||||
return new AIFunctionMcpServerPrompt(function, prompt);
|
||||
}
|
||||
|
||||
private static McpServerPromptCreateOptions? DeriveOptions(MethodInfo method, McpServerPromptCreateOptions? options)
|
||||
{
|
||||
McpServerPromptCreateOptions newOptions = options?.Clone() ?? new();
|
||||
|
||||
if (method.GetCustomAttribute<McpServerPromptAttribute>() is { } attr)
|
||||
{
|
||||
newOptions.Name ??= attr.Name;
|
||||
}
|
||||
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
/// <summary>Gets the <see cref="AIFunction"/> wrapped by this prompt.</summary>
|
||||
internal AIFunction AIFunction { get; }
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="McpServerPrompt"/> class.</summary>
|
||||
private AIFunctionMcpServerPrompt(AIFunction function, Prompt prompt)
|
||||
{
|
||||
AIFunction = function;
|
||||
ProtocolPrompt = prompt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => AIFunction.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Prompt ProtocolPrompt { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<GetPromptResult> GetAsync(
|
||||
RequestContext<GetPromptRequestParams> request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(request);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context.
|
||||
Dictionary<string, object?> arguments = request.Params?.Arguments is IDictionary<string, object?> existingArgs ?
|
||||
new(existingArgs) :
|
||||
[];
|
||||
arguments[RequestContextKey] = request;
|
||||
|
||||
object? result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result switch
|
||||
{
|
||||
GetPromptResult getPromptResult => getPromptResult,
|
||||
|
||||
string text => new()
|
||||
{
|
||||
Description = ProtocolPrompt.Description,
|
||||
Messages = [new() { Role = Role.User, Content = new() { Text = text, Type = "text" } }],
|
||||
},
|
||||
|
||||
PromptMessage promptMessage => new()
|
||||
{
|
||||
Description = ProtocolPrompt.Description,
|
||||
Messages = [promptMessage],
|
||||
},
|
||||
|
||||
IEnumerable<PromptMessage> promptMessages => new()
|
||||
{
|
||||
Description = ProtocolPrompt.Description,
|
||||
Messages = [.. promptMessages],
|
||||
},
|
||||
|
||||
ChatMessage chatMessage => new()
|
||||
{
|
||||
Description = ProtocolPrompt.Description,
|
||||
Messages = [.. chatMessage.ToPromptMessages()],
|
||||
},
|
||||
|
||||
IEnumerable<ChatMessage> chatMessages => new()
|
||||
{
|
||||
Description = ProtocolPrompt.Description,
|
||||
Messages = [.. chatMessages.SelectMany(chatMessage => chatMessage.ToPromptMessages())],
|
||||
},
|
||||
|
||||
null => throw new InvalidOperationException($"Null result returned from prompt function."),
|
||||
|
||||
_ => throw new InvalidOperationException($"Unknown result type '{result.GetType()}' returned from prompt function."),
|
||||
};
|
||||
}
|
||||
}
|
||||
34
src/ModelContextProtocol/Server/DelegatingMcpServerPrompt.cs
Normal file
34
src/ModelContextProtocol/Server/DelegatingMcpServerPrompt.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Provides an <see cref="McpServerPrompt"/> that delegates all operations to an inner <see cref="McpServerPrompt"/>.</summary>
|
||||
/// <remarks>
|
||||
/// This is recommended as a base type when building prompts that can be chained around an underlying <see cref="McpServerPrompt"/>.
|
||||
/// The default implementation simply passes each call to the inner prompt instance.
|
||||
/// </remarks>
|
||||
public abstract class DelegatingMcpServerPrompt : McpServerPrompt
|
||||
{
|
||||
private readonly McpServerPrompt _innerPrompt;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="DelegatingMcpServerPrompt"/> class around the specified <paramref name="innerPrompt"/>.</summary>
|
||||
/// <param name="innerPrompt">The inner prompt wrapped by this delegating prompt.</param>
|
||||
protected DelegatingMcpServerPrompt(McpServerPrompt innerPrompt)
|
||||
{
|
||||
Throw.IfNull(innerPrompt);
|
||||
_innerPrompt = innerPrompt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Prompt ProtocolPrompt => _innerPrompt.ProtocolPrompt;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<GetPromptResult> GetAsync(
|
||||
RequestContext<GetPromptRequestParams> request,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
_innerPrompt.GetAsync(request, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => _innerPrompt.ToString();
|
||||
}
|
||||
8
src/ModelContextProtocol/Server/IMcpServerPrimitive.cs
Normal file
8
src/ModelContextProtocol/Server/IMcpServerPrimitive.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Represents an MCP server primitive, like a tool or a prompt.</summary>
|
||||
public interface IMcpServerPrimitive
|
||||
{
|
||||
/// <summary>Gets the name of the primitive.</summary>
|
||||
string Name { get; }
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
{
|
||||
private readonly IServerTransport? _serverTransport;
|
||||
private readonly EventHandler? _toolsChangedDelegate;
|
||||
private readonly EventHandler? _promptsChangedDelegate;
|
||||
|
||||
private ITransport? _sessionTransport;
|
||||
private string _endpointName;
|
||||
|
|
@ -77,6 +78,13 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
Method = NotificationMethods.ToolListChangedNotification,
|
||||
});
|
||||
};
|
||||
_promptsChangedDelegate = delegate
|
||||
{
|
||||
_ = SendMessageAsync(new JsonRpcNotification()
|
||||
{
|
||||
Method = NotificationMethods.PromptListChangedNotification,
|
||||
});
|
||||
};
|
||||
|
||||
AddNotificationHandler(NotificationMethods.InitializedNotification, _ =>
|
||||
{
|
||||
|
|
@ -85,6 +93,11 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
tools.Changed += _toolsChangedDelegate;
|
||||
}
|
||||
|
||||
if (ServerOptions.Capabilities?.Prompts?.PromptCollection is { } prompts)
|
||||
{
|
||||
prompts.Changed += _promptsChangedDelegate;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
|
|
@ -160,6 +173,11 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
tools.Changed -= _toolsChangedDelegate;
|
||||
}
|
||||
|
||||
if (ServerOptions.Capabilities?.Prompts?.PromptCollection is { } prompts)
|
||||
{
|
||||
prompts.Changed -= _promptsChangedDelegate;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await base.DisposeUnsynchronizedAsync().ConfigureAwait(false);
|
||||
|
|
@ -253,15 +271,97 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
|
||||
private void SetPromptsHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Prompts is not { } promptsCapability)
|
||||
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
|
||||
var listPromptsHandler = promptsCapability?.ListPromptsHandler;
|
||||
var getPromptHandler = promptsCapability?.GetPromptHandler;
|
||||
var prompts = promptsCapability?.PromptCollection;
|
||||
|
||||
if (listPromptsHandler is null != getPromptHandler is null)
|
||||
{
|
||||
return;
|
||||
throw new McpServerException("ListPrompts and GetPrompt handlers should be specified together.");
|
||||
}
|
||||
|
||||
if (promptsCapability.ListPromptsHandler is not { } listPromptsHandler ||
|
||||
promptsCapability.GetPromptHandler is not { } getPromptHandler)
|
||||
// Handle tools provided via DI.
|
||||
if (prompts is { IsEmpty: false })
|
||||
{
|
||||
throw new McpServerException("Prompts capability was enabled, but ListPrompts and/or GetPrompt handlers were not specified.");
|
||||
var originalListPromptsHandler = listPromptsHandler;
|
||||
var originalGetPromptHandler = getPromptHandler;
|
||||
|
||||
// Synthesize the handlers, making sure a ToolsCapability is specified.
|
||||
listPromptsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListPromptsResult result = new();
|
||||
foreach (McpServerPrompt prompt in prompts)
|
||||
{
|
||||
result.Prompts.Add(prompt.ProtocolPrompt);
|
||||
}
|
||||
|
||||
if (originalListPromptsHandler is not null)
|
||||
{
|
||||
string? nextCursor = null;
|
||||
do
|
||||
{
|
||||
ListPromptsResult extraResults = await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(false);
|
||||
result.Prompts.AddRange(extraResults.Prompts);
|
||||
|
||||
nextCursor = extraResults.NextCursor;
|
||||
if (nextCursor is not null)
|
||||
{
|
||||
request = request with { Params = new() { Cursor = nextCursor } };
|
||||
}
|
||||
}
|
||||
while (nextCursor is not null);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
getPromptHandler = (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null ||
|
||||
!prompts.TryGetPrimitive(request.Params.Name, out var prompt))
|
||||
{
|
||||
if (originalGetPromptHandler is not null)
|
||||
{
|
||||
return originalGetPromptHandler(request, cancellationToken);
|
||||
}
|
||||
|
||||
throw new McpServerException($"Unknown prompt '{request.Params?.Name}'");
|
||||
}
|
||||
|
||||
return prompt.GetAsync(request, cancellationToken);
|
||||
};
|
||||
|
||||
ServerCapabilities = new()
|
||||
{
|
||||
Experimental = options.Capabilities?.Experimental,
|
||||
Logging = options.Capabilities?.Logging,
|
||||
Tools = options.Capabilities?.Tools,
|
||||
Resources = options.Capabilities?.Resources,
|
||||
Prompts = new()
|
||||
{
|
||||
ListPromptsHandler = listPromptsHandler,
|
||||
GetPromptHandler = getPromptHandler,
|
||||
PromptCollection = prompts,
|
||||
ListChanged = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerCapabilities = options.Capabilities;
|
||||
|
||||
if (promptsCapability is null)
|
||||
{
|
||||
// No prompts, and no prompts capability was declared, so nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the handlers are provided if the capability is enabled.
|
||||
if (listPromptsHandler is null || getPromptHandler is null)
|
||||
{
|
||||
throw new McpServerException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>(RequestMethods.PromptsList, (request, ct) => listPromptsHandler(new(this, request), ct));
|
||||
|
|
@ -318,7 +418,7 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
callToolHandler = (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null ||
|
||||
!tools.TryGetTool(request.Params.Name, out var tool))
|
||||
!tools.TryGetPrimitive(request.Params.Name, out var tool))
|
||||
{
|
||||
if (originalCallToolHandler is not null)
|
||||
{
|
||||
|
|
|
|||
163
src/ModelContextProtocol/Server/McpServerPrimitiveCollection.cs
Normal file
163
src/ModelContextProtocol/Server/McpServerPrimitiveCollection.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
using ModelContextProtocol.Utils;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Provides a thread-safe collection of <typeparamref name="T"/> instances, indexed by their names.</summary>
|
||||
/// <typeparam name="T">Specifies the type of primitive stored in the collection.</typeparam>
|
||||
public class McpServerPrimitiveCollection<T> : ICollection<T>, IReadOnlyCollection<T>
|
||||
where T : IMcpServerPrimitive
|
||||
{
|
||||
/// <summary>Concurrent dictionary of primitives, indexed by their names.</summary>
|
||||
private readonly ConcurrentDictionary<string, T> _primitives = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpServerPrimitiveCollection{T}"/> class.
|
||||
/// </summary>
|
||||
public McpServerPrimitiveCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Occurs when the collection is changed.</summary>
|
||||
/// <remarks>
|
||||
/// By default, this is raised when a primitive is added or removed. However, a derived implementation
|
||||
/// may raise this event for other reasons, such as when a primitive is modified.
|
||||
/// </remarks>
|
||||
public event EventHandler? Changed;
|
||||
|
||||
/// <summary>Gets the number of primitives in the collection.</summary>
|
||||
public int Count => _primitives.Count;
|
||||
|
||||
/// <summary>Gets whether there are any primitives in the collection.</summary>
|
||||
public bool IsEmpty => _primitives.IsEmpty;
|
||||
|
||||
/// <summary>Raises <see cref="Changed"/> if there are registered handlers.</summary>
|
||||
protected void RaiseChanged() => Changed?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
/// <summary>Gets the <typeparamref name="T"/> with the specified <paramref name="name"/> from the collection.</summary>
|
||||
/// <param name="name">The name of the primitive to retrieve.</param>
|
||||
/// <returns>The <typeparamref name="T"/> with the specified name.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="KeyNotFoundException">An primitive with the specified name does not exist in the collection.</exception>
|
||||
public T this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
Throw.IfNull(name);
|
||||
return _primitives[name];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears all primitives from the collection.</summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
_primitives.Clear();
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified <typeparamref name="T"/> to the collection.</summary>
|
||||
/// <param name="primitive">The primitive to be added.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">A primitive with the same name as <paramref name="primitive"/> already exists in the collection.</exception>
|
||||
public void Add(T primitive)
|
||||
{
|
||||
if (!TryAdd(primitive))
|
||||
{
|
||||
throw new ArgumentException($"A primitive with the same name '{primitive.Name}' already exists in the collection.", nameof(primitive));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified <typeparamref name="T"/> to the collection.</summary>
|
||||
/// <param name="primitive">The primitive to be added.</param>
|
||||
/// <returns><see langword="true"/> if the primitive was added; otherwise, <see langword="false"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool TryAdd(T primitive)
|
||||
{
|
||||
Throw.IfNull(primitive);
|
||||
|
||||
bool added = _primitives.TryAdd(primitive.Name, primitive);
|
||||
if (added)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified primitivefrom the collection.</summary>
|
||||
/// <param name="primitive">The primitive to be removed from the collection.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the primitive was found in the collection and removed; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool Remove(T primitive)
|
||||
{
|
||||
Throw.IfNull(primitive);
|
||||
|
||||
bool removed = ((ICollection<KeyValuePair<string, T>>)_primitives).Remove(new(primitive.Name, primitive));
|
||||
if (removed)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to get the primitive with the specified name from the collection.</summary>
|
||||
/// <param name="name">The name of the primitive to retrieve.</param>
|
||||
/// <param name="primitive">The primitive, if found; otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the primitive was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool TryGetPrimitive(string name, [NotNullWhen(true)] out T? primitive)
|
||||
{
|
||||
Throw.IfNull(name);
|
||||
return _primitives.TryGetValue(name, out primitive);
|
||||
}
|
||||
|
||||
/// <summary>Checks if a specific primitive is present in the collection of primitives.</summary>
|
||||
/// <param name="primitive">The primitive to search for in the collection.</param>
|
||||
/// <see langword="true"/> if the primitive was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// <exception cref="ArgumentNullException"><paramref name="primitive"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool Contains(T primitive)
|
||||
{
|
||||
Throw.IfNull(primitive);
|
||||
return ((ICollection<KeyValuePair<string, T>>)_primitives).Contains(new(primitive.Name, primitive));
|
||||
}
|
||||
|
||||
/// <summary>Gets the names of all of the primitives in the collection.</summary>
|
||||
public virtual ICollection<string> PrimitiveNames => _primitives.Keys;
|
||||
|
||||
/// <summary>Creates an array containing all of the primitives in the collection.</summary>
|
||||
/// <returns>An array containing all of the primitives in the collection.</returns>
|
||||
public virtual T[] ToArray() => _primitives.Values.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
Throw.IfNull(array);
|
||||
|
||||
_primitives.Values.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
foreach (var entry in _primitives)
|
||||
{
|
||||
yield return entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ICollection<T>.IsReadOnly => false;
|
||||
}
|
||||
95
src/ModelContextProtocol/Server/McpServerPrompt.cs
Normal file
95
src/ModelContextProtocol/Server/McpServerPrompt.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Represents an invocable prompt used by Model Context Protocol servers.</summary>
|
||||
public abstract class McpServerPrompt : IMcpServerPrimitive
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="McpServerPrompt"/> class.</summary>
|
||||
protected McpServerPrompt()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets the protocol <see cref="Prompt"/> type for this instance.</summary>
|
||||
public abstract Prompt ProtocolPrompt { get; }
|
||||
|
||||
/// <summary>Invokes the <see cref="McpServerPrompt"/>.</summary>
|
||||
/// <param name="request">The request information resulting in the invocation of this tool.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The call response from invoking the tool.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
|
||||
public abstract Task<GetPromptResult> GetAsync(
|
||||
RequestContext<GetPromptRequestParams> request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to be represented via the created <see cref="McpServerPrompt"/>.</param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
|
||||
/// <returns>The created <see cref="McpServerPrompt"/> for invoking <paramref name="method"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerPrompt Create(
|
||||
Delegate method,
|
||||
McpServerPromptCreateOptions? options = null) =>
|
||||
AIFunctionMcpServerPrompt.Create(method, options);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via a <see cref="Delegate"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="method">The method to be represented via the created <see cref="McpServerPrompt"/>.</param>
|
||||
/// <param name="target">The instance if <paramref name="method"/> is an instance method; otherwise, <see langword="null"/>.</param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
|
||||
/// <returns>The created <see cref="McpServerPrompt"/> for invoking <paramref name="method"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="method"/> is an instance method but <paramref name="target"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerPrompt Create(
|
||||
MethodInfo method,
|
||||
object? target = null,
|
||||
McpServerPromptCreateOptions? options = null) =>
|
||||
AIFunctionMcpServerPrompt.Create(method, target, options);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="McpServerPrompt"/> instance for a method, specified via an <see cref="MethodInfo"/> for
|
||||
/// and instance method, along with a <see cref="Type"/> representing the type of the target object to
|
||||
/// instantiate each time the method is invoked.
|
||||
/// </summary>
|
||||
/// <param name="method">The instance method to be represented via the created <see cref="AIFunction"/>.</param>
|
||||
/// <param name="targetType">
|
||||
/// The <see cref="Type"/> to construct an instance of on which to invoke <paramref name="method"/> when
|
||||
/// the resulting <see cref="AIFunction"/> is invoked. If services are provided,
|
||||
/// ActivatorUtilities.CreateInstance will be used to construct the instance using those services; otherwise,
|
||||
/// <see cref="Activator.CreateInstance(Type)"/> is used, utilizing the type's public parameterless constructor.
|
||||
/// If an instance can't be constructed, an exception is thrown during the function's invocation.
|
||||
/// </param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
|
||||
/// <returns>The created <see cref="AIFunction"/> for invoking <paramref name="method"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerPrompt Create(
|
||||
MethodInfo method,
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType,
|
||||
McpServerPromptCreateOptions? options = null) =>
|
||||
AIFunctionMcpServerPrompt.Create(method, targetType, options);
|
||||
|
||||
/// <summary>Creates an <see cref="McpServerPrompt"/> that wraps the specified <see cref="AIFunction"/>.</summary>
|
||||
/// <param name="function">The function to wrap.</param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerPrompt"/> to control its behavior.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// Unlike the other overloads of Create, the <see cref="McpServerPrompt"/> created by <see cref="Create(AIFunction, McpServerPromptCreateOptions)"/>
|
||||
/// does not provide all of the special parameter handling for MCP-specific concepts, like <see cref="IMcpServer"/>.
|
||||
/// </remarks>
|
||||
public static McpServerPrompt Create(
|
||||
AIFunction function,
|
||||
McpServerPromptCreateOptions? options = null) =>
|
||||
AIFunctionMcpServerPrompt.Create(function, options);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => ProtocolPrompt.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
string IMcpServerPrimitive.Name => ProtocolPrompt.Name;
|
||||
}
|
||||
19
src/ModelContextProtocol/Server/McpServerPromptAttribute.cs
Normal file
19
src/ModelContextProtocol/Server/McpServerPromptAttribute.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicate that a method should be considered an MCP prompt and describe it.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class McpServerPromptAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpServerPromptAttribute"/> class.
|
||||
/// </summary>
|
||||
public McpServerPromptAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Gets the name of the prompt.</summary>
|
||||
/// <remarks>If <see langword="null"/>, the method name will be used.</remarks>
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Provides options for controlling the creation of an <see cref="McpServerPrompt"/>.</summary>
|
||||
public sealed class McpServerPromptCreateOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets optional services used in the construction of the <see cref="McpServerPrompt"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These services will be used to determine which parameters should be satisifed from dependency injection; what services
|
||||
/// are satisfied via this provider should match what's satisfied via the provider passed in at invocation time.
|
||||
/// </remarks>
|
||||
public IServiceProvider? Services { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name to use for the <see cref="McpServerPrompt"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see langword="null"/>, but an <see cref="McpServerPromptAttribute"/> is applied to the method,
|
||||
/// the name from the attribute will be used. If that's not present, a name based on the method's name will be used.
|
||||
/// </remarks>
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the description to use for the <see cref="McpServerPrompt"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see langword="null"/>, but a <see cref="DescriptionAttribute"/> is applied to the method,
|
||||
/// the description from that attribute will be used.
|
||||
/// </remarks>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow clone of the current <see cref="McpServerPromptCreateOptions"/> instance.
|
||||
/// </summary>
|
||||
internal McpServerPromptCreateOptions Clone() =>
|
||||
new McpServerPromptCreateOptions()
|
||||
{
|
||||
Services = Services,
|
||||
Name = Name,
|
||||
Description = Description,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Used to attribute a type containing methods that should be exposed as MCP prompts.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily relevant to methods that scan types in an assembly looking for methods
|
||||
/// to expose, such as <see cref="McpServerBuilderExtensions.WithPromptsFromAssembly"/>. It is not
|
||||
/// necessary to attribute types explicitly provided to a method like <see cref="McpServerBuilderExtensions.WithPrompts{TPrompt}"/>.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class McpServerPromptTypeAttribute : Attribute;
|
||||
|
|
@ -6,7 +6,7 @@ using System.Reflection;
|
|||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Represents an invocable tool used by Model Context Protocol clients and servers.</summary>
|
||||
public abstract class McpServerTool
|
||||
public abstract class McpServerTool : IMcpServerPrimitive
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="McpServerTool"/> class.</summary>
|
||||
protected McpServerTool()
|
||||
|
|
@ -89,4 +89,7 @@ public abstract class McpServerTool
|
|||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => ProtocolTool.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
string IMcpServerPrimitive.Name => ProtocolTool.Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public sealed class McpServerToolAttribute : Attribute
|
|||
internal bool? _readOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpServerToolTypeAttribute"/> class.
|
||||
/// Initializes a new instance of the <see cref="McpServerToolAttribute"/> class.
|
||||
/// </summary>
|
||||
public McpServerToolAttribute()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
using ModelContextProtocol.Utils;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ModelContextProtocol.Server;
|
||||
|
||||
/// <summary>Provides a thread-safe collection of <see cref="McpServerTool"/> instances, indexed by their names.</summary>
|
||||
public class McpServerToolCollection : ICollection<McpServerTool>, IReadOnlyCollection<McpServerTool>
|
||||
{
|
||||
/// <summary>Concurrent dictionary of tools, indexed by their names.</summary>
|
||||
private readonly ConcurrentDictionary<string, McpServerTool> _tools = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpServerToolCollection"/> class.
|
||||
/// </summary>
|
||||
public McpServerToolCollection()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Occurs when the collection is changed.</summary>
|
||||
/// <remarks>
|
||||
/// By default, this is raised when a tool is added or removed. However, a derived implementation
|
||||
/// may raise this event for other reasons, such as when a tool is modified.
|
||||
/// </remarks>
|
||||
public event EventHandler? Changed;
|
||||
|
||||
/// <summary>Gets the number of tools in the collection.</summary>
|
||||
public int Count => _tools.Count;
|
||||
|
||||
/// <summary>Gets whether there are any tools in the collection.</summary>
|
||||
public bool IsEmpty => _tools.IsEmpty;
|
||||
|
||||
/// <summary>Raises <see cref="Changed"/> if there are registered handlers.</summary>
|
||||
protected void RaiseChanged() => Changed?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
/// <summary>Gets the <see cref="McpServerTool"/> with the specified <paramref name="name"/> from the collection.</summary>
|
||||
/// <param name="name">The name of the tool to retrieve.</param>
|
||||
/// <returns>The <see cref="McpServerTool"/> with the specified name.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="KeyNotFoundException">A tool with the specified name does not exist in the collection.</exception>
|
||||
public McpServerTool this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
Throw.IfNull(name);
|
||||
return _tools[name];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears all tools from the collection.</summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
_tools.Clear();
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified <see cref="McpServerTool"/> to the collection.</summary>
|
||||
/// <param name="tool">The tool to be added.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="tool"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">A tool with the same name as <paramref name="tool"/> already exists in the collection.</exception>
|
||||
public void Add(McpServerTool tool)
|
||||
{
|
||||
if (!TryAdd(tool))
|
||||
{
|
||||
throw new ArgumentException($"A tool with the same name '{tool.ProtocolTool.Name}' already exists in the collection.", nameof(tool));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified <see cref="McpServerTool"/> to the collection.</summary>
|
||||
/// <param name="tool">The tool to be added.</param>
|
||||
/// <returns><see langword="true"/> if the tool was added; otherwise, <see langword="false"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="tool"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool TryAdd(McpServerTool tool)
|
||||
{
|
||||
Throw.IfNull(tool);
|
||||
|
||||
bool added = _tools.TryAdd(tool.ProtocolTool.Name, tool);
|
||||
if (added)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified toolfrom the collection.</summary>
|
||||
/// <param name="tool">The tool to be removed from the collection.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the tool was found in the collection and removed; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="tool"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool Remove(McpServerTool tool)
|
||||
{
|
||||
Throw.IfNull(tool);
|
||||
|
||||
bool removed = ((ICollection<KeyValuePair<string, McpServerTool>>)_tools).Remove(new(tool.ProtocolTool.Name, tool));
|
||||
if (removed)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>Attempts to get the tool with the specified name from the collection.</summary>
|
||||
/// <param name="name">The name of the tool to retrieve.</param>
|
||||
/// <param name="tool">The tool, if found; otherwise, <see langword="null"/>.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if the tool was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool TryGetTool(string name, [NotNullWhen(true)] out McpServerTool? tool)
|
||||
{
|
||||
Throw.IfNull(name);
|
||||
return _tools.TryGetValue(name, out tool);
|
||||
}
|
||||
|
||||
/// <summary>Checks if a specific tool is present in the collection of tools.</summary>
|
||||
/// <param name="tool">The tool to search for in the collection.</param>
|
||||
/// <see langword="true"/> if the tool was found in the collection and return; otherwise, <see langword="false"/> if it couldn't be found.
|
||||
/// <exception cref="ArgumentNullException"><paramref name="tool"/> is <see langword="null"/>.</exception>
|
||||
public virtual bool Contains(McpServerTool tool)
|
||||
{
|
||||
Throw.IfNull(tool);
|
||||
return ((ICollection<KeyValuePair<string, McpServerTool>>)_tools).Contains(new(tool.ProtocolTool.Name, tool));
|
||||
}
|
||||
|
||||
/// <summary>Gets the names of all of the tools in the collection.</summary>
|
||||
public virtual ICollection<string> ToolNames => _tools.Keys;
|
||||
|
||||
/// <summary>Creates an array containing all of the tools in the collection.</summary>
|
||||
/// <returns>An array containing all of the tools in the collection.</returns>
|
||||
public virtual McpServerTool[] ToArray() => _tools.Values.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void CopyTo(McpServerTool[] array, int arrayIndex)
|
||||
{
|
||||
Throw.IfNull(array);
|
||||
|
||||
foreach (var entry in _tools)
|
||||
{
|
||||
array[arrayIndex++] = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IEnumerator<McpServerTool> GetEnumerator()
|
||||
{
|
||||
foreach (var entry in _tools)
|
||||
{
|
||||
yield return entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator<McpServerTool> IEnumerable<McpServerTool>.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool ICollection<McpServerTool>.IsReadOnly => false;
|
||||
}
|
||||
|
|
@ -201,9 +201,16 @@ internal static partial class TemporaryAIFunctionFactory
|
|||
throw new ArgumentException("Open generic methods are not supported", nameof(method));
|
||||
}
|
||||
|
||||
if (!method.IsStatic && target is null)
|
||||
if (method.IsStatic)
|
||||
{
|
||||
throw new ArgumentNullException("Target must not be null for an instance method.", nameof(target));
|
||||
if (target is not null)
|
||||
{
|
||||
throw new ArgumentException("The specified method is a static method but the specified target is non-null.", nameof(target));
|
||||
}
|
||||
}
|
||||
else if (target is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target), "The specified method is an instance method but the specified target is null.");
|
||||
}
|
||||
|
||||
var functionDescriptor = ReflectionAIFunctionDescriptor.GetOrCreate(method, options);
|
||||
|
|
|
|||
Loading…
Reference in a new issue