diff --git a/src/FunctionsMcpTool.csproj b/src/FunctionsMcpTool.csproj index d83cdba..6b35247 100644 --- a/src/FunctionsMcpTool.csproj +++ b/src/FunctionsMcpTool.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 v4 Exe enable @@ -10,13 +10,15 @@ - - + + - - + + + - + + diff --git a/src/Program.cs b/src/Program.cs index fc36219..02cedd9 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -16,6 +16,6 @@ // input bindings: builder .ConfigureMcpTool(GetSnippetToolName) - .WithProperty(SnippetNamePropertyName, PropertyType, SnippetNamePropertyDescription, required: true); + .WithProperty(SnippetNamePropertyName, McpToolPropertyType.String, SnippetNamePropertyDescription, required: true); builder.Build().Run(); diff --git a/src/QRCodeTool.cs b/src/QRCodeTool.cs new file mode 100644 index 0000000..a96592b --- /dev/null +++ b/src/QRCodeTool.cs @@ -0,0 +1,33 @@ +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Extensions.Mcp; +using Microsoft.Extensions.Logging; +using ModelContextProtocol.Protocol; +using QRCoder; + +namespace FunctionsSnippetTool; + +public class QRCodeTool(ILogger logger) +{ + [Function(nameof(GenerateQRCode))] + public ImageContentBlock GenerateQRCode( + [McpToolTrigger(nameof(GenerateQRCode), "Generates a QR code for the provided text or URL.")] + ToolInvocationContext context, + [McpToolProperty(nameof(data), "Text or URL to encode", true)] + string data) + { + logger.LogInformation("Generating QR code for: {Data}", data); + + using var qrGenerator = new QRCodeGenerator(); + using var qrCodeData = qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q); + using var qrCode = new PngByteQRCode(qrCodeData); + + var qrCodeBytes = qrCode.GetGraphic(20); + var base64Data = Convert.ToBase64String(qrCodeBytes); + + return new ImageContentBlock + { + Data = base64Data, + MimeType = "image/png" + }; + } +} diff --git a/src/SnippetsTool.cs b/src/SnippetsTool.cs index a5234aa..faedaf3 100644 --- a/src/SnippetsTool.cs +++ b/src/SnippetsTool.cs @@ -1,5 +1,6 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Extensions.Mcp; +using ModelContextProtocol.Protocol; using static FunctionsSnippetTool.ToolsInformation; namespace FunctionsSnippetTool; @@ -7,6 +8,8 @@ namespace FunctionsSnippetTool; public class SnippetsTool() { private const string BlobPath = "snippets/{mcptoolargs." + SnippetNamePropertyName + "}.json"; + private const string BlobPathCompare1 = "snippets/{mcptoolargs." + FirstSnippetPropertyName + "}.json"; + private const string BlobPathCompare2 = "snippets/{mcptoolargs." + SecondSnippetPropertyName + "}.json"; [Function(nameof(GetSnippet))] public object GetSnippet( @@ -31,4 +34,87 @@ string snippet { return snippet; } + + [Function(nameof(CompareSnippets))] + public IList CompareSnippets( + [McpToolTrigger(CompareSnippetsToolName, CompareSnippetsToolDescription)] + ToolInvocationContext context, + [McpToolProperty(FirstSnippetPropertyName, FirstSnippetPropertyDescription, true)] + string snippet1, + [McpToolProperty(SecondSnippetPropertyName, SecondSnippetPropertyDescription, true)] + string snippet2, + [BlobInput(BlobPathCompare1)] string? content1, + [BlobInput(BlobPathCompare2)] string? content2 + ) + { + var lines1 = content1?.Split('\n').Length ?? 0; + var lines2 = content2?.Split('\n').Length ?? 0; + var chars1 = content1?.Length ?? 0; + var chars2 = content2?.Length ?? 0; + var charDiff = chars2 - chars1; + + var diff = GenerateDiff(content1 ?? "", content2 ?? ""); + + return new List + { + new TextContentBlock + { + Text = $"## Comparison: {snippet1} vs {snippet2}\n\n" + + $"**{snippet1}:** {lines1} lines, {chars1} characters\n" + + $"**{snippet2}:** {lines2} lines, {chars2} characters\n" + + $"**Difference:** {(charDiff > 0 ? "+" : "")}{charDiff} characters\n\n" + + $"### Unified Diff\n```diff\n{diff}\n```" + }, + new TextContentBlock + { + Text = content1 is not null + ? $"### Content of {snippet1}\n```{content1}\n```" + : $"### Content of {snippet1} not found." + }, + new TextContentBlock + { + Text = content2 is not null + ? $"### Content of {snippet2}\n```{content2}\n```" + : $"### Content of {snippet2} not found." + } + }; + } + + private static string GenerateDiff(string content1, string content2) + { + var lines1 = content1.Split('\n'); + var lines2 = content2.Split('\n'); + var result = new System.Text.StringBuilder(); + var maxLines = Math.Max(lines1.Length, lines2.Length); + + for (int i = 0; i < maxLines; i++) + { + var line1 = i < lines1.Length ? lines1[i] : null; + var line2 = i < lines2.Length ? lines2[i] : null; + + if (line1 == line2) + { + // Same line + result.AppendLine($" {line1}"); + } + else if (line1 is not null && line2 is not null) + { + // Modified line + result.AppendLine($"- {line1}"); + result.AppendLine($"+ {line2}"); + } + else if (line1 is not null) + { + // Removed line + result.AppendLine($"- {line1}"); + } + else if (line2 is not null) + { + // Added line + result.AppendLine($"+ {line2}"); + } + } + + return result.ToString().TrimEnd(); + } } diff --git a/src/ToolsInformation.cs b/src/ToolsInformation.cs index 649243c..12f34ef 100644 --- a/src/ToolsInformation.cs +++ b/src/ToolsInformation.cs @@ -12,8 +12,14 @@ internal sealed class ToolsInformation public const string SnippetPropertyName = "snippet"; public const string SnippetNamePropertyDescription = "The name of the snippet."; public const string SnippetPropertyDescription = "The code snippet."; - public const string PropertyType = "string"; public const string HelloToolName = "hello"; public const string HelloToolDescription = "Simple hello world MCP Tool that responses with a hello message."; + public const string CompareSnippetsToolName = "compare_snippets"; + public const string CompareSnippetsToolDescription = + "Compares two code snippets side by side with statistics."; + public const string FirstSnippetPropertyName = "snippet1"; + public const string FirstSnippetPropertyDescription = "Name of the first snippet to compare."; + public const string SecondSnippetPropertyName = "snippet2"; + public const string SecondSnippetPropertyDescription = "Name of the second snippet to compare."; }