using System.Text.Json.Nodes;
using Sbroenne.ExcelMcp.McpServer.Prompts;
namespace Sbroenne.ExcelMcp.McpServer.Completions;
/// <summary>
/// Provides autocomplete suggestions for Excel MCP prompts and resources.
/// Implements completion support by handling the completion/complete JSON-RPC method
/// as described in https://devblogs.microsoft.com/dotnet/mcp-csharp-sdk-2025-06-18-update/
/// </summary>
public static class ExcelCompletionHandler
{
/// <summary>
/// Handle completion requests according to MCP spec.
/// Returns suggestions for prompt arguments based on the argument name and context.
/// </summary>
public static JsonObject HandleCompletion(JsonObject request)
{
try
{
var paramsObj = request["params"] as JsonObject;
if (paramsObj == null)
{
return CreateEmptyCompletion();
}
var refObj = paramsObj["ref"] as JsonObject;
var argument = paramsObj["argument"] as JsonObject;
if (refObj == null || argument == null)
{
return CreateEmptyCompletion();
}
var refType = refObj["type"]?.ToString();
var argumentName = argument["name"]?.ToString();
var argumentValue = argument["value"]?.ToString() ?? "";
// Handle prompt argument completions
if (refType == "ref/prompt")
{
var suggestions = GetPromptArgumentCompletions(argumentName, argumentValue);
return CreateCompletionResult(suggestions);
}
// Handle resource URI completions
if (refType == "ref/resource")
{
var uri = refObj["uri"]?.ToString() ?? "";
var suggestions = GetResourceUriCompletions(uri, argumentValue);
return CreateCompletionResult(suggestions);
}
return CreateEmptyCompletion();
}
catch
{
return CreateEmptyCompletion();
}
}
private static List<string> GetPromptArgumentCompletions(string? argumentName, string currentValue)
{
var suggestions = new List<string>();
// NOTE: Action parameter completions are NOT needed here!
// The MCP SDK auto-generates tool schema from C# enums (PowerQueryAction, RangeAction, etc.)
// and LLMs receive all valid enum values directly in the schema.
//
// Only provide completions for freeform string parameters where suggestions add value.
// Load destination completions for Power Query
if (argumentName == "loadDestination")
{
suggestions = MarkdownLoader.LoadCompletionValues("load_destination.md");
}
// Privacy level completions
else if (argumentName == "privacyLevel")
{
suggestions = MarkdownLoader.LoadCompletionValues("privacy_level.md");
}
// Validation type completions
else if (argumentName == "validationType")
{
suggestions = MarkdownLoader.LoadCompletionValues("validation_types.md");
}
// Validation operator completions
else if (argumentName == "validationOperator")
{
suggestions = MarkdownLoader.LoadCompletionValues("validation_operators.md");
}
// Border style completions
else if (argumentName == "borderStyle")
{
suggestions = MarkdownLoader.LoadCompletionValues("border_styles.md");
}
// Border weight completions
else if (argumentName == "borderWeight")
{
suggestions = MarkdownLoader.LoadCompletionValues("border_weights.md");
}
// Table style completions
else if (argumentName == "tableStyle")
{
suggestions = MarkdownLoader.LoadCompletionValues("table_styles.md");
}
return FilterSuggestions(suggestions, currentValue);
}
private static List<string> GetResourceUriCompletions(string uri, string currentValue)
{
var suggestions = new List<string>();
// Excel file path completions
if (uri.StartsWith("file://", StringComparison.OrdinalIgnoreCase) ||
uri.Contains(".xlsx", StringComparison.OrdinalIgnoreCase) ||
uri.Contains(".xlsm", StringComparison.OrdinalIgnoreCase))
{
suggestions =
[
"C:\\Data\\workbook.xlsx",
"C:\\Reports\\financial-report.xlsx",
"C:\\Projects\\analysis.xlsm",
"workbook.xlsx",
"report.xlsx"
];
}
return FilterSuggestions(suggestions, currentValue);
}
private static List<string> FilterSuggestions(List<string> suggestions, string currentValue)
{
if (string.IsNullOrWhiteSpace(currentValue))
{
return suggestions;
}
// Case-insensitive prefix matching
return suggestions
.Where(s => s.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase))
.ToList();
}
private static JsonObject CreateCompletionResult(List<string> suggestions)
{
var values = new JsonArray();
foreach (var item in suggestions.Select(suggestion => new JsonObject
{
["value"] = suggestion,
["description"] = $"Autocomplete: {suggestion}"
}))
{
values.Add(item);
}
return new JsonObject
{
["values"] = values,
["total"] = suggestions.Count,
["hasMore"] = false
};
}
private static JsonObject CreateEmptyCompletion()
{
return new JsonObject
{
["values"] = new JsonArray(),
["total"] = 0,
["hasMore"] = false
};
}
}