Auto-populate completion handlers from AllowedValuesAttribute on prompt/resource parameters (#1380)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
parent
06563e233d
commit
4af2596019
5 changed files with 415 additions and 1 deletions
|
|
@ -245,12 +245,62 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
var completeHandler = options.Handlers.CompleteHandler;
|
||||
var completionsCapability = options.Capabilities?.Completions;
|
||||
|
||||
if (completeHandler is null && completionsCapability is null)
|
||||
// Build completion value lookups from prompt/resource collections' [AllowedValues]-attributed parameters.
|
||||
Dictionary<string, Dictionary<string, string[]>>? promptCompletions = BuildAllowedValueCompletions(options.PromptCollection);
|
||||
Dictionary<string, Dictionary<string, string[]>>? resourceCompletions = BuildAllowedValueCompletions(options.ResourceCollection);
|
||||
bool hasCollectionCompletions = promptCompletions is not null || resourceCompletions is not null;
|
||||
|
||||
if (completeHandler is null && completionsCapability is null && !hasCollectionCompletions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
completeHandler ??= (static async (_, __) => new CompleteResult());
|
||||
|
||||
// Augment the completion handler with allowed values from prompt/resource collections.
|
||||
if (hasCollectionCompletions)
|
||||
{
|
||||
var originalCompleteHandler = completeHandler;
|
||||
completeHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
CompleteResult result = await originalCompleteHandler(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string[]? allowedValues = null;
|
||||
switch (request.Params?.Ref)
|
||||
{
|
||||
case PromptReference pr when promptCompletions is not null:
|
||||
if (promptCompletions.TryGetValue(pr.Name, out var promptParams))
|
||||
{
|
||||
promptParams.TryGetValue(request.Params.Argument.Name, out allowedValues);
|
||||
}
|
||||
break;
|
||||
|
||||
case ResourceTemplateReference rtr when resourceCompletions is not null:
|
||||
if (rtr.Uri is not null && resourceCompletions.TryGetValue(rtr.Uri, out var resourceParams))
|
||||
{
|
||||
resourceParams.TryGetValue(request.Params.Argument.Name, out allowedValues);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (allowedValues is not null)
|
||||
{
|
||||
string partialValue = request.Params!.Argument.Value;
|
||||
foreach (var v in allowedValues)
|
||||
{
|
||||
if (v.StartsWith(partialValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.Completion.Values.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
result.Completion.Total = result.Completion.Values.Count;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
completeHandler = BuildFilterPipeline(completeHandler, options.Filters.Request.CompleteFilters);
|
||||
|
||||
ServerCapabilities.Completions = new();
|
||||
|
|
@ -262,6 +312,76 @@ internal sealed partial class McpServerImpl : McpServer
|
|||
McpJsonUtilities.JsonContext.Default.CompleteResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a lookup of primitive name/URI → (parameter name → allowed values) from the enum values
|
||||
/// in the JSON schemas of AIFunction-based prompts or resources.
|
||||
/// </summary>
|
||||
private static Dictionary<string, Dictionary<string, string[]>>? BuildAllowedValueCompletions<T>(
|
||||
McpServerPrimitiveCollection<T>? primitives) where T : class, IMcpServerPrimitive
|
||||
{
|
||||
if (primitives is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, Dictionary<string, string[]>>? result = null;
|
||||
foreach (var primitive in primitives)
|
||||
{
|
||||
JsonElement schema;
|
||||
string id;
|
||||
if (primitive is AIFunctionMcpServerPrompt aiPrompt)
|
||||
{
|
||||
schema = aiPrompt.AIFunction.JsonSchema;
|
||||
id = aiPrompt.ProtocolPrompt.Name;
|
||||
}
|
||||
else if (primitive is AIFunctionMcpServerResource aiResource && aiResource.IsTemplated)
|
||||
{
|
||||
schema = aiResource.AIFunction.JsonSchema;
|
||||
id = aiResource.ProtocolResourceTemplate.UriTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (schema.TryGetProperty("properties", out JsonElement properties) &&
|
||||
properties.ValueKind is JsonValueKind.Object)
|
||||
{
|
||||
Dictionary<string, string[]>? paramValues = null;
|
||||
foreach (var param in properties.EnumerateObject())
|
||||
{
|
||||
if (param.Value.TryGetProperty("enum", out JsonElement enumValues) &&
|
||||
enumValues.ValueKind is JsonValueKind.Array)
|
||||
{
|
||||
List<string>? values = null;
|
||||
foreach (var item in enumValues.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind is JsonValueKind.String && item.GetString() is { } str)
|
||||
{
|
||||
values ??= [];
|
||||
values.Add(str);
|
||||
}
|
||||
}
|
||||
|
||||
if (values is not null)
|
||||
{
|
||||
paramValues ??= new(StringComparer.Ordinal);
|
||||
paramValues[param.Name] = [.. values];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paramValues is not null)
|
||||
{
|
||||
result ??= new(StringComparer.Ordinal);
|
||||
result[id] = paramValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ConfigureExperimentalAndExtensions(McpServerOptions options)
|
||||
{
|
||||
ServerCapabilities.Experimental = options.Capabilities?.Experimental;
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ namespace ModelContextProtocol.Server;
|
|||
/// <para>
|
||||
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Parameters of type <see cref="string"/> that are decorated with <c>AllowedValuesAttribute</c>
|
||||
/// will automatically have their allowed values surfaced as completions in response to <c>completion/complete</c> requests from clients,
|
||||
/// without requiring a custom <see cref="McpServerHandlers.CompleteHandler"/> to be configured.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class McpServerPrompt : IMcpServerPrimitive
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ namespace ModelContextProtocol.Server;
|
|||
/// <para>
|
||||
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Parameters of type <see cref="string"/> that are decorated with <c>AllowedValuesAttribute</c>
|
||||
/// will automatically have their allowed values surfaced as completions in response to <c>completion/complete</c> requests from clients,
|
||||
/// without requiring a custom <see cref="McpServerHandlers.CompleteHandler"/> to be configured.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class McpServerPromptAttribute : Attribute
|
||||
|
|
|
|||
|
|
@ -121,6 +121,11 @@ namespace ModelContextProtocol.Server;
|
|||
/// <para>
|
||||
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Parameters of type <see cref="string"/> that are decorated with <c>AllowedValuesAttribute</c>
|
||||
/// will automatically have their allowed values surfaced as completions in response to <c>completion/complete</c> requests from clients,
|
||||
/// without requiring a custom <see cref="McpServerHandlers.CompleteHandler"/> to be configured.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class McpServerResource : IMcpServerPrimitive
|
||||
{
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ namespace ModelContextProtocol.Server;
|
|||
/// <para>
|
||||
/// Other returned types will result in an <see cref="InvalidOperationException"/> being thrown.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Parameters of type <see cref="string"/> that are decorated with <c>AllowedValuesAttribute</c>
|
||||
/// will automatically have their allowed values surfaced as completions in response to <c>completion/complete</c> requests from clients,
|
||||
/// without requiring a custom <see cref="McpServerHandlers.CompleteHandler"/> to be configured.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class McpServerResourceAttribute : Attribute
|
||||
|
|
|
|||
Loading…
Reference in a new issue