Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/modelcontextprotocol/csharp-sdk/llms.txt

Use this file to discover all available pages before exploring further.

The MCP C# SDK supports argument completion, allowing servers to provide auto-complete suggestions for prompt arguments and resource URI parameters.

Overview

Clients can request completion suggestions as users type argument values. This improves user experience by:
  • Suggesting valid values for parameters
  • Helping discover available options
  • Reducing errors from invalid input
  • Speeding up parameter entry

Implementing Completions

Use the WithCompleteHandler method to configure a completion handler:
Program.cs
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithPrompts<MyPrompts>()
    .WithResources<MyResources>()
    .WithCompleteHandler(async (ctx, ct) =>
    {
        if (ctx.Params is not { } @params)
        {
            throw new NotSupportedException("Params are required.");
        }

        var @ref = @params.Ref;
        var argument = @params.Argument;

        // Handle prompt argument completion
        if (@ref is PromptReference pr)
        {
            return await CompletePromptArgument(pr, argument);
        }

        // Handle resource URI parameter completion
        if (@ref is ResourceTemplateReference rtr)
        {
            return await CompleteResourceParameter(rtr, argument);
        }

        throw new NotSupportedException($"Unknown reference type: {@ref.Type}");
    });

var app = builder.Build();
app.MapMcp();
app.Run();

Prompt Argument Completions

Provide suggestions for prompt arguments:
static Task<CompleteResult> CompletePromptArgument(
    PromptReference promptRef,
    CompletionArgument argument)
{
    // Define completions for different arguments
    var completions = new Dictionary<string, IEnumerable<string>>
    {
        { "style", ["casual", "formal", "technical", "friendly"] },
        { "temperature", ["0", "0.5", "0.7", "1.0"] },
        { "language", ["python", "javascript", "csharp", "java", "go"] },
        { "complexity", ["beginner", "intermediate", "advanced", "expert"] }
    };

    // Get matching values for the argument
    if (!completions.TryGetValue(argument.Name, out var values))
    {
        throw new NotSupportedException($"Unknown argument name: {argument.Name}");
    }

    // Filter by what the user has typed
    var matchingValues = values
        .Where(v => v.StartsWith(argument.Value, StringComparison.OrdinalIgnoreCase))
        .ToList();

    return Task.FromResult(new CompleteResult
    {
        Completion = new Completion
        {
            Values = matchingValues,
            HasMore = false,
            Total = matchingValues.Count
        }
    });
}

Example Usage

Given this prompt:
[McpServerPrompt(Name = "code_review")]
[Description("Generate a code review prompt")]
public static string CodeReview(
    [Description("Programming language")] string language,
    [Description("Code complexity level")] string complexity)
{
    return $"Review this {language} code at {complexity} level";
}
When a client requests completion for language with value "py", the server returns ["python"].

Resource URI Parameter Completions

Provide suggestions for resource template parameters:
static Task<CompleteResult> CompleteResourceParameter(
    ResourceTemplateReference resourceRef,
    CompletionArgument argument)
{
    // Extract parameter name from URI template
    // e.g., "file://{category}/{filename}" -> complete "category" or "filename"
    
    var uri = resourceRef.Uri;
    var parameterName = ExtractParameterName(uri, argument.Value);

    var completions = parameterName switch
    {
        "category" => new[] { "documents", "images", "videos", "audio" },
        "id" => new[] { "1", "2", "3", "4", "5" },
        "status" => new[] { "active", "pending", "completed", "archived" },
        _ => Array.Empty<string>()
    };

    var matchingValues = completions
        .Where(v => v.StartsWith(argument.Value, StringComparison.OrdinalIgnoreCase))
        .ToList();

    return Task.FromResult(new CompleteResult
    {
        Completion = new Completion
        {
            Values = matchingValues,
            HasMore = false,
            Total = matchingValues.Count
        }
    });
}

static string ExtractParameterName(string uriTemplate, string partialValue)
{
    // Parse URI template and determine which parameter is being completed
    // This is a simplified example - production code would need more robust parsing
    var lastSlash = uriTemplate.LastIndexOf('/');
    var lastPart = uriTemplate.Substring(lastSlash + 1);
    
    if (lastPart.StartsWith("{") && lastPart.EndsWith("}"))
    {
        return lastPart.Trim('{', '}');
    }
    
    return "id"; // default
}

Example Usage

Given this resource template:
[McpServerResource(
    UriTemplate = "file://{category}/{filename}",
    Name = "File Resource"
)]
public static string GetFile(string category, string filename)
{
    return $"Content of {category}/{filename}";
}
When completing category with value "doc", the server returns ["documents"].

Complete Result Structure

Values
List<string>
required
Array of completion values matching the user’s input.
Total
int
Total number of available completions (before filtering by user input).
HasMore
bool
Whether there are more completions available beyond what’s returned.

Pagination Support

For large completion sets, implement pagination:
.WithCompleteHandler(async (ctx, ct) =>
{
    var @params = ctx.Params;
    var argument = @params.Argument;
    
    // Get all possible values
    var allValues = await GetAllCompletionValues(argument.Name);
    
    // Filter by user input
    var filteredValues = allValues
        .Where(v => v.StartsWith(argument.Value, StringComparison.OrdinalIgnoreCase))
        .ToList();
    
    // Implement pagination
    const int pageSize = 50;
    var page = 0; // Extract from context if needed
    var pagedValues = filteredValues
        .Skip(page * pageSize)
        .Take(pageSize)
        .ToList();
    
    return new CompleteResult
    {
        Completion = new Completion
        {
            Values = pagedValues,
            HasMore = filteredValues.Count > (page + 1) * pageSize,
            Total = filteredValues.Count
        }
    };
});

Dynamic Completions

Generate completions dynamically from databases or APIs:
public class CompletionService
{
    private readonly IDbContext _dbContext;
    private readonly IHttpClientFactory _httpClientFactory;

    public async Task<IEnumerable<string>> GetUserCompletions(
        string prefix,
        CancellationToken cancellationToken)
    {
        return await _dbContext.Users
            .Where(u => u.Username.StartsWith(prefix))
            .Select(u => u.Username)
            .Take(20)
            .ToListAsync(cancellationToken);
    }

    public async Task<IEnumerable<string>> GetCategoryCompletions(
        string prefix,
        CancellationToken cancellationToken)
    {
        var client = _httpClientFactory.CreateClient("CatalogApi");
        var response = await client.GetFromJsonAsync<CategoryResponse>(
            $"/categories?prefix={prefix}",
            cancellationToken);
        return response?.Categories ?? [];
    }
}
Use the service in the completion handler:
.WithCompleteHandler(async (ctx, ct) =>
{
    var services = ctx.Services;
    var completionService = services.GetRequiredService<CompletionService>();
    
    var @params = ctx.Params;
    var argument = @params.Argument;
    
    var values = argument.Name switch
    {
        "username" => await completionService.GetUserCompletions(argument.Value, ct),
        "category" => await completionService.GetCategoryCompletions(argument.Value, ct),
        _ => Enumerable.Empty<string>()
    };
    
    var valueList = values.ToList();
    
    return new CompleteResult
    {
        Completion = new Completion
        {
            Values = valueList,
            HasMore = false,
            Total = valueList.Count
        }
    };
});

Completion Context

The RequestContext<CompleteRequestParams> provides:
ctx.Params.Ref        // PromptReference or ResourceTemplateReference
ctx.Params.Argument   // CompletionArgument with Name and Value
ctx.Server           // McpServer instance
ctx.Services         // IServiceProvider for DI

CompletionArgument Properties

Name
string
required
The name of the argument being completed.
Value
string
required
The partial value entered by the user so far.

Error Handling

Handle completion errors appropriately:
.WithCompleteHandler(async (ctx, ct) =>
{
    try
    {
        var @params = ctx.Params;
        
        if (@params?.Ref is not PromptReference && @params?.Ref is not ResourceTemplateReference)
        {
            throw new McpProtocolException(
                "Invalid reference type for completion",
                McpErrorCode.InvalidParams);
        }
        
        // ... handle completion
    }
    catch (Exception ex) when (ex is not McpException)
    {
        throw new McpException($"Completion failed: {ex.Message}");
    }
});

Complete Example

Here’s a complete implementation from the EverythingServer sample:
Program.cs
var exampleCompletions = new Dictionary<string, IEnumerable<string>>
{
    { "style", ["casual", "formal", "technical", "friendly"] },
    { "temperature", ["0", "0.5", "0.7", "1.0"] },
    { "resourceId", ["1", "2", "3", "4", "5"] }
};

builder.Services.AddMcpServer()
    .WithCompleteHandler(async (ctx, ct) =>
    {
        var @params = ctx.Params ?? throw new NotSupportedException("Params are required.");
        var @ref = @params.Ref;
        var argument = @params.Argument;

        if (@ref is ResourceTemplateReference rtr)
        {
            var resourceId = rtr.Uri?.Split("/").Last();
            if (resourceId is null)
            {
                return new CompleteResult();
            }

            var values = exampleCompletions["resourceId"]
                .Where(id => id.StartsWith(argument.Value));

            return new CompleteResult
            {
                Completion = new Completion
                {
                    Values = [.. values],
                    HasMore = false,
                    Total = values.Count()
                }
            };
        }

        if (@ref is PromptReference pr)
        {
            if (!exampleCompletions.TryGetValue(argument.Name, out var completionValues))
            {
                throw new NotSupportedException($"Unknown argument name: {argument.Name}");
            }

            var values = completionValues.Where(value => value.StartsWith(argument.Value));
            return new CompleteResult
            {
                Completion = new Completion
                {
                    Values = [.. values],
                    HasMore = false,
                    Total = values.Count()
                }
            };
        }

        throw new NotSupportedException($"Unknown reference type: {@ref.Type}");
    });

Best Practices

1

Fast Response Times

Keep completion handlers fast. Use caching for frequently accessed data and implement timeouts for external calls.
2

Case-Insensitive Matching

Use case-insensitive matching to improve user experience: StringComparison.OrdinalIgnoreCase.
3

Limit Result Count

Return a reasonable number of completions (typically 10-50) to avoid overwhelming clients.
4

Sort Results

Return completions in a logical order - alphabetically, by relevance, or by usage frequency.
5

Handle Empty Input

When argument.Value is empty, return the most relevant or popular completions.

Next Steps

  • Learn about Tools for executable functions
  • Explore Prompts for prompt templates
  • Add Filters for cross-cutting concerns

Build docs developers (and LLMs) love