Add McpClient/Session methods using .Protocol types (#1063)
This commit is contained in:
parent
d76bc16bd1
commit
4a84412331
14 changed files with 1116 additions and 639 deletions
|
|
@ -137,12 +137,12 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
|||
Description = options?.Description ?? function.Description,
|
||||
Arguments = args,
|
||||
Icons = options?.Icons,
|
||||
};
|
||||
|
||||
// Populate Meta from options and/or McpMetaAttribute instances if a MethodInfo is available
|
||||
prompt.Meta = function.UnderlyingMethod is not null ?
|
||||
AIFunctionMcpServerTool.CreateMetaFromAttributes(function.UnderlyingMethod, options?.Meta, options?.SerializerOptions) :
|
||||
options?.Meta;
|
||||
// Populate Meta from options and/or McpMetaAttribute instances if a MethodInfo is available
|
||||
Meta = function.UnderlyingMethod is not null ?
|
||||
AIFunctionMcpServerTool.CreateMetaFromAttributes(function.UnderlyingMethod, options?.Meta) :
|
||||
options?.Meta
|
||||
};
|
||||
|
||||
return new AIFunctionMcpServerPrompt(function, prompt, options?.Metadata ?? []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
|||
MimeType = options?.MimeType ?? "application/octet-stream",
|
||||
Icons = options?.Icons,
|
||||
Meta = function.UnderlyingMethod is not null ?
|
||||
AIFunctionMcpServerTool.CreateMetaFromAttributes(function.UnderlyingMethod, options?.Meta, options?.SerializerOptions) :
|
||||
AIFunctionMcpServerTool.CreateMetaFromAttributes(function.UnderlyingMethod, options?.Meta) :
|
||||
options?.Meta,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
|||
|
||||
// Populate Meta from options and/or McpMetaAttribute instances if a MethodInfo is available
|
||||
tool.Meta = function.UnderlyingMethod is not null ?
|
||||
CreateMetaFromAttributes(function.UnderlyingMethod, options.Meta, options.SerializerOptions) :
|
||||
CreateMetaFromAttributes(function.UnderlyingMethod, options.Meta) :
|
||||
options.Meta;
|
||||
}
|
||||
|
||||
|
|
@ -361,9 +361,8 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
|||
/// <summary>Creates a Meta <see cref="JsonObject"/> from <see cref="McpMetaAttribute"/> instances on the specified method.</summary>
|
||||
/// <param name="method">The method to extract <see cref="McpMetaAttribute"/> instances from.</param>
|
||||
/// <param name="meta">Optional <see cref="JsonObject"/> to seed the Meta with. Properties from this object take precedence over attributes.</param>
|
||||
/// <param name="serializerOptions">Optional <see cref="JsonSerializerOptions"/> to use for serialization. This parameter is ignored when parsing JSON strings from attributes.</param>
|
||||
/// <returns>A <see cref="JsonObject"/> with metadata, or null if no metadata is present.</returns>
|
||||
internal static JsonObject? CreateMetaFromAttributes(MethodInfo method, JsonObject? meta = null, JsonSerializerOptions? serializerOptions = null)
|
||||
internal static JsonObject? CreateMetaFromAttributes(MethodInfo method, JsonObject? meta = null)
|
||||
{
|
||||
// Transfer all McpMetaAttribute instances to the Meta JsonObject, ignoring any that would overwrite existing properties.
|
||||
foreach (var attr in method.GetCustomAttributes<McpMetaAttribute>())
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public abstract class DelegatingMcpServerPrompt : McpServerPrompt
|
|||
|
||||
/// <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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="innerPrompt"/> is <see langword="null"/>.</exception>
|
||||
protected DelegatingMcpServerPrompt(McpServerPrompt innerPrompt)
|
||||
{
|
||||
Throw.IfNull(innerPrompt);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public abstract class DelegatingMcpServerResource : McpServerResource
|
|||
|
||||
/// <summary>Initializes a new instance of the <see cref="DelegatingMcpServerResource"/> class around the specified <paramref name="innerResource"/>.</summary>
|
||||
/// <param name="innerResource">The inner resource wrapped by this delegating resource.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="innerResource"/> is <see langword="null"/>.</exception>
|
||||
protected DelegatingMcpServerResource(McpServerResource innerResource)
|
||||
{
|
||||
Throw.IfNull(innerResource);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public abstract class DelegatingMcpServerTool : McpServerTool
|
|||
|
||||
/// <summary>Initializes a new instance of the <see cref="DelegatingMcpServerTool"/> class around the specified <paramref name="innerTool"/>.</summary>
|
||||
/// <param name="innerTool">The inner tool wrapped by this delegating tool.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="innerTool"/> is <see langword="null"/>.</exception>
|
||||
protected DelegatingMcpServerTool(McpServerTool innerTool)
|
||||
{
|
||||
Throw.IfNull(innerTool);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace ModelContextProtocol.Server;
|
|||
/// <typeparam name="TParams">The type of the parameters sent with the request.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the response returned by the handler.</typeparam>
|
||||
/// <param name="request">The request context containing the parameters and other metadata.</param>
|
||||
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the result of the handler.</returns>
|
||||
public delegate ValueTask<TResult> McpRequestHandler<TParams, TResult>(
|
||||
RequestContext<TParams> request,
|
||||
|
|
|
|||
|
|
@ -47,18 +47,21 @@ public abstract partial class McpServer : McpSession
|
|||
/// <summary>
|
||||
/// Requests to sample an LLM via the client using the specified request parameters.
|
||||
/// </summary>
|
||||
/// <param name="request">The parameters for the sampling request.</param>
|
||||
/// <param name="requestParams">The parameters for the sampling request.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A task containing the sampling result from the client.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
|
||||
public ValueTask<CreateMessageResult> SampleAsync(
|
||||
CreateMessageRequestParams request, CancellationToken cancellationToken = default)
|
||||
CreateMessageRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfSamplingUnsupported();
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.SamplingCreateMessage,
|
||||
request,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CreateMessageResult,
|
||||
cancellationToken: cancellationToken);
|
||||
|
|
@ -157,7 +160,7 @@ public abstract partial class McpServer : McpSession
|
|||
_ => null,
|
||||
};
|
||||
|
||||
var result = await SampleAsync(new()
|
||||
var result = await SampleAsync(new CreateMessageRequestParams
|
||||
{
|
||||
MaxTokens = chatOptions?.MaxOutputTokens ?? ServerOptions.MaxSamplingOutputTokens,
|
||||
Messages = samplingMessages,
|
||||
|
|
@ -181,14 +184,16 @@ public abstract partial class McpServer : McpSession
|
|||
|
||||
return new(new ChatMessage(result.Role is Role.User ? ChatRole.User : ChatRole.Assistant, responseContents))
|
||||
{
|
||||
ModelId = result.Model,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
FinishReason = result.StopReason switch
|
||||
{
|
||||
CreateMessageResult.StopReasonEndTurn => ChatFinishReason.Stop,
|
||||
CreateMessageResult.StopReasonMaxTokens => ChatFinishReason.Length,
|
||||
CreateMessageResult.StopReasonStopSequence => ChatFinishReason.Stop,
|
||||
CreateMessageResult.StopReasonToolUse => ChatFinishReason.ToolCalls,
|
||||
CreateMessageResult.StopReasonEndTurn or CreateMessageResult.StopReasonStopSequence => ChatFinishReason.Stop,
|
||||
_ => null,
|
||||
}
|
||||
},
|
||||
ModelId = result.Model,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -200,31 +205,33 @@ public abstract partial class McpServer : McpSession
|
|||
public IChatClient AsSamplingChatClient()
|
||||
{
|
||||
ThrowIfSamplingUnsupported();
|
||||
|
||||
return new SamplingChatClient(this);
|
||||
}
|
||||
|
||||
/// <summary>Gets an <see cref="ILogger"/> on which logged messages will be sent as notifications to the client.</summary>
|
||||
/// <returns>An <see cref="ILogger"/> that can be used to log to the client.</returns>
|
||||
public ILoggerProvider AsClientLoggerProvider()
|
||||
{
|
||||
return new ClientLoggerProvider(this);
|
||||
}
|
||||
public ILoggerProvider AsClientLoggerProvider() =>
|
||||
new ClientLoggerProvider(this);
|
||||
|
||||
/// <summary>
|
||||
/// Requests the client to list the roots it exposes.
|
||||
/// </summary>
|
||||
/// <param name="request">The parameters for the list roots request.</param>
|
||||
/// <param name="requestParams">The parameters for the list roots request.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A task containing the list of roots exposed by the client.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The client does not support roots.</exception>
|
||||
public ValueTask<ListRootsResult> RequestRootsAsync(
|
||||
ListRootsRequestParams request, CancellationToken cancellationToken = default)
|
||||
ListRootsRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfRootsUnsupported();
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.RootsList,
|
||||
request,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ListRootsResult,
|
||||
cancellationToken: cancellationToken);
|
||||
|
|
@ -233,19 +240,21 @@ public abstract partial class McpServer : McpSession
|
|||
/// <summary>
|
||||
/// Requests additional information from the user via the client, allowing the server to elicit structured data.
|
||||
/// </summary>
|
||||
/// <param name="request">The parameters for the elicitation request.</param>
|
||||
/// <param name="requestParams">The parameters for the elicitation request.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A task containing the elicitation result.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">The client does not support elicitation.</exception>
|
||||
public ValueTask<ElicitResult> ElicitAsync(
|
||||
ElicitRequestParams request, CancellationToken cancellationToken = default)
|
||||
ElicitRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(request);
|
||||
ThrowIfElicitationUnsupported(request);
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfElicitationUnsupported(requestParams);
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.ElicitationCreate,
|
||||
request,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ElicitRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ElicitResult,
|
||||
cancellationToken: cancellationToken);
|
||||
|
|
@ -257,33 +266,40 @@ public abstract partial class McpServer : McpSession
|
|||
/// </summary>
|
||||
/// <typeparam name="T">The type describing the expected input shape. Only primitive members are supported (string, number, boolean, enum).</typeparam>
|
||||
/// <param name="message">The message to present to the user.</param>
|
||||
/// <param name="serializerOptions">Serializer options that influence property naming and deserialization.</param>
|
||||
/// <param name="options">Optional request options including metadata, serialization settings, and progress tracking.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>An <see cref="ElicitResult{T}"/> with the user's response, if accepted.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="message"/> is empty or composed entirely of whitespace.</exception>
|
||||
/// <exception cref="InvalidOperationException">The client does not support elicitation.</exception>
|
||||
/// <remarks>
|
||||
/// Elicitation uses a constrained subset of JSON Schema and only supports strings, numbers/integers, booleans and string enums.
|
||||
/// Unsupported member types are ignored when constructing the schema.
|
||||
/// </remarks>
|
||||
public async ValueTask<ElicitResult<T>> ElicitAsync<T>(
|
||||
string message,
|
||||
JsonSerializerOptions? serializerOptions = null,
|
||||
RequestOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||
Throw.IfNullOrWhiteSpace(message);
|
||||
|
||||
var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions;
|
||||
serializerOptions.MakeReadOnly();
|
||||
|
||||
var dict = s_elicitResultSchemaCache.GetValue(serializerOptions, _ => new());
|
||||
|
||||
var schema = dict.GetOrAdd(typeof(T),
|
||||
#if NET
|
||||
var schema = dict.GetOrAdd(typeof(T), static (t, s) => BuildRequestSchema(t, s), serializerOptions);
|
||||
static (t, s) => BuildRequestSchema(t, s), serializerOptions);
|
||||
#else
|
||||
var schema = dict.GetOrAdd(typeof(T), type => BuildRequestSchema(type, serializerOptions));
|
||||
type => BuildRequestSchema(type, serializerOptions));
|
||||
#endif
|
||||
|
||||
var request = new ElicitRequestParams
|
||||
{
|
||||
Message = message,
|
||||
RequestedSchema = schema,
|
||||
Meta = options?.GetMetaForRequest(),
|
||||
};
|
||||
|
||||
ThrowIfElicitationUnsupported(request);
|
||||
|
|
@ -295,7 +311,7 @@ public abstract partial class McpServer : McpSession
|
|||
return new ElicitResult<T> { Action = raw.Action, Content = default };
|
||||
}
|
||||
|
||||
var obj = new JsonObject();
|
||||
JsonObject obj = [];
|
||||
foreach (var kvp in raw.Content)
|
||||
{
|
||||
obj[kvp.Key] = JsonNode.Parse(kvp.Value.GetRawText());
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ public abstract class McpServerPrompt : IMcpServerPrimitive
|
|||
/// </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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> or <paramref name="createTargetFunc"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerPrompt Create(
|
||||
MethodInfo method,
|
||||
Func<RequestContext<GetPromptRequestParams>, object> createTargetFunc,
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ public abstract class McpServerResource : IMcpServerPrimitive
|
|||
/// <returns>
|
||||
/// <see langword="true"/> if the <paramref name="uri"/> matches the <see cref="ProtocolResourceTemplate"/>; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="uri"/> is <see langword="null"/>.</exception>
|
||||
public abstract bool IsMatch(string uri);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -235,7 +236,7 @@ public abstract class McpServerResource : IMcpServerPrimitive
|
|||
/// </param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerResource"/> 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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> or <paramref name="createTargetFunc"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerResource Create(
|
||||
MethodInfo method,
|
||||
Func<RequestContext<ReadResourceRequestParams>, object> createTargetFunc,
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ public abstract class McpServerTool : IMcpServerPrimitive
|
|||
/// </param>
|
||||
/// <param name="options">Optional options used in the creation of the <see cref="McpServerTool"/> 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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="method"/> or <paramref name="createTargetFunc"/> is <see langword="null"/>.</exception>
|
||||
public static McpServerTool Create(
|
||||
MethodInfo method,
|
||||
Func<RequestContext<CallToolRequestParams>, object> createTargetFunc,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public sealed class RequestContext<TParams>
|
|||
/// </summary>
|
||||
/// <param name="server">The server with which this instance is associated.</param>
|
||||
/// <param name="jsonRpcRequest">The JSON-RPC request associated with this context.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="server"/> or <paramref name="jsonRpcRequest"/> is <see langword="null"/>.</exception>
|
||||
public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest)
|
||||
{
|
||||
Throw.IfNull(server);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ public sealed class SseResponseStreamTransport(Stream sseResponseStream, string?
|
|||
/// <param name="message">The JSON-RPC message received from the client.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation to buffer the JSON-RPC message for processing.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">There is an attempt to process a message before calling <see cref="RunAsync(CancellationToken)"/>.</exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,11 @@ public sealed class StreamableHttpServerTransport : ITransport
|
|||
/// <param name="sseResponseStream">The response stream to write MCP JSON-RPC messages as SSE events to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the send loop that writes JSON-RPC messages to the SSE response stream.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="sseResponseStream"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// <see cref="Stateless"/> is <see langword="true"/> and GET requests are not supported in stateless mode,
|
||||
/// or a GET request has already been started for this session.
|
||||
/// </exception>
|
||||
public async Task HandleGetRequestAsync(Stream sseResponseStream, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(sseResponseStream);
|
||||
|
|
@ -100,13 +105,14 @@ public sealed class StreamableHttpServerTransport : ITransport
|
|||
/// to the <see cref="JsonRpcRequest"/> that initiated the message.
|
||||
/// </summary>
|
||||
/// <param name="message">The JSON-RPC message received from the client via the POST request body.</param>
|
||||
/// <param name="cancellationToken">This token allows for the operation to be canceled if needed. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="responseStream">The POST response body to write MCP JSON-RPC messages to.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if data was written to the response body.
|
||||
/// <see false="false"/> if nothing was written because the request body did not contain any <see cref="JsonRpcRequest"/> messages to respond to.
|
||||
/// The HTTP application should typically respond with an empty "202 Accepted" response in this scenario.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="message"/> or <paramref name="responseStream"/> is <see langword="null"/>.</exception>
|
||||
/// <remarks>
|
||||
/// If an authenticated <see cref="ClaimsPrincipal"/> sent the message, that can be included in the <see cref="JsonRpcMessage.Context"/>.
|
||||
/// No other part of the context should be set.
|
||||
|
|
|
|||
Loading…
Reference in a new issue