Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/chatmodes/math.chatmode.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
---
description: 'An Ask mode for math-related queries, which can render LaTeX equations.'
tools: ['vscodeAPI', 'latex']
tools: ['latex']
---
Actively use the #latex_markdown tool to render LaTeX equations in your responses as inline markdown images to enhance clarity and visual appeal. This tool is particularly useful for displaying mathematical equations, formulas, and other LaTeX-rendered content in a visually engaging manner.

Before invoking #latex_markdown, retrieve the user's theme using the #vscodeAPI to ensure the LaTeX rendering is compatible with their current theme. This will help maintain consistency in the appearance of rendered content across different user interfaces.

Always place the resulting markdown image from the #latex_markdown tool in its own
line to ensure proper formatting and visibility.
Actively use the #latex tool to render LaTeX equations in your responses as inline markdown images to enhance clarity and visual appeal. This tool is particularly useful for displaying mathematical equations, formulas, and other LaTeX-rendered content in a visually engaging manner.
9 changes: 7 additions & 2 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
"latex": {
"type": "stdio",
"command": "dotnet",
"cwd": "${workspaceFolder}${/}mcp",
"args": [
"run",
"${workspaceFolder}${/}samples${/}latex.cs"
]
"latex.cs",
],
"env": {
// Enables Edit and Continue
"COMPLUS_ForceENC": "1",
}
}
}
}
66 changes: 0 additions & 66 deletions samples/latex.cs

This file was deleted.

1 change: 1 addition & 0 deletions src/MCPDemo/MCPDemo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetConfig.Configuration" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.7" />
<PackageReference Include="ModelContextProtocol" Version="0.3.0-preview.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
Expand Down
153 changes: 123 additions & 30 deletions src/MCPDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,140 @@
using SixLabors.ImageSharp;
using System.Diagnostics;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

var builder = App.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Configuration.AddDotNetConfig();

var initialized = false;
bool? darkMode = bool.TryParse(builder.Configuration["latex:darkMode"], out var dm) ? dm : null;
string? fontSize = builder.Configuration["latex:fontSize"];
// See https://editor.codecogs.com/docs/4-LaTeX_rendering.php#overview_anchor
var fonts = new Dictionary<string, string>
{
{ "Tiny", "tiny" },
{ "Small", "small" },
{ "Large", "large" },
{ "LARGE", "LARGE" },
{ "Huge", "huge"}
};

Debugger.Launch();

builder.Services
.AddHttpClient()
.AddMcpServer()
.WithStdioServerTransport()
.WithTool(
[Description("Converts LaTeX equations into markdown-formatted images for display inline.")] async
(IHttpClientFactory httpFactory,
[Description("The LaTeX equation to render.")] string latex,
[Description("Use dark mode by inverting the colors in the output.")] bool darkMode) =>
{
var colors = darkMode ? @"\bg{black}\fg{white}" : @"\bg{white}\fg{black}";
var query = WebUtility.UrlEncode(@"\small\dpi{300}" + colors + latex);
var url = $"https://latex.codecogs.com/png.image?{query}";
using var client = httpFactory.CreateClient();
using var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
name: "latex",
title: "LaTeX to Image",
description: "Converts LaTeX equations into markdown-formatted images for inline display.",
tool: async (IHttpClientFactory httpFactory, IMcpServer server,
[Description("The LaTeX equation to render.")] string latex)
=>
{
// On first tool run, we ask for preferences for dark mode and font size.
if (!initialized)
{
initialized = true;
(darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize);
}

var colors = darkMode switch
{
true => @"\fg{white}",
false => @"\fg{black}",
null => @"\bg{white}\fg{black}"
};

var query = WebUtility.UrlEncode(@"\dpi{300}\" + (fontSize ?? "small") + colors + new string([.. latex.Where(c => !char.IsWhiteSpace(c))]));
var url = $"https://latex.codecogs.com/png.image?{query}";

using var client = httpFactory.CreateClient();
using var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();

using var image = Image.Load<Rgba32>(await response.Content.ReadAsStreamAsync());
using var ms = new MemoryStream();
image.SaveAsPng(ms);
var base64 = Convert.ToBase64String(ms.ToArray());
return
$"""
![{latex}](
data:image/png;base64,{base64}
)
""";
}
else
return $"> ![LaTeX Equation](data:image/png;base64,{base64})";
})
.WithTool(
name: "latex_getprefs",
title: "Get LaTeX Preferences",
description: "Gets the saved LaTeX rendering preferences for dark mode and font size.",
tool: () => new { darkMode, fontSize })
.WithTool(
name: "latex_setprefs",
title: "Set LaTeX Preferences",
description: "Sets the LaTeX rendering preferences for dark mode and font size.",
tool: async (IMcpServer server,
[Description("Use dark mode by inverting the colors in the output.")] bool? darkMode = null,
[Description("Font size to use in the output: tiny=5pt, small=9pt, large=12pt, LARGE=18pt, huge=20pt")] string? fontSize = null)
=> (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize));

await builder.Build().RunAsync();

/// <summary>Saves the LaTeX rendering preferences to configuration.</summary>
async ValueTask<(bool? darkMode, string? fontSize)> SetPreferences(IMcpServer server, bool? darkMode, string? fontSize)
{
if ((darkMode is null || fontSize is null || !fonts.ContainsValue(fontSize)) && server.ClientCapabilities?.Elicitation != null)
{
var result = await server.ElicitAsync(new()
{
Message = "Specify LaTeX rendering preferences",
RequestedSchema = new()
{
return
$"""
```latex
{latex}
```
> {response.ReasonPhrase}
""";
Required = ["darkMode", "fontSize"],
Properties =
{
{ "darkMode", new ElicitRequestParams.BooleanSchema()
{
Title = "Dark Mode",
Description = "Use dark mode?",
Default = darkMode
}
},
{ "fontSize", new ElicitRequestParams.EnumSchema()
{
Title = "Font Size",
Description = "Font size to use for the LaTeX rendering.",
Enum = [.. fonts.Values],
EnumNames = [.. fonts.Keys],
}
},
},
}
});

await builder.Build().RunAsync();
if (result.Action == "accept" && result.Content is { } content)
{
darkMode = content["darkMode"].GetBoolean();
fontSize = content["fontSize"].GetString() ?? "tiny";

DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global)
.GetSection("latex")
.SetBoolean("darkMode", darkMode.Value)
.SetString("fontSize", fontSize);
}
// action == cancel is not supported in vscode
// actoin == decline would be equal to "ignore" so we just don't set anything.
return (darkMode, fontSize);
}
else
{
// We persist to ~/.netconfig
var config = DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global).GetSection("latex");
if (darkMode != null)
config = config.SetBoolean("darkMode", darkMode.Value);
if (fontSize != null && fonts.ContainsValue(fontSize))
config = config.SetString("fontSize", fontSize);
else
fontSize = null;

return (darkMode, fontSize);
}
}

39 changes: 38 additions & 1 deletion src/Smith/McpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,46 @@ public static class McpExtensions
/// Registers a specific method as a server tool.
/// </summary>
public IMcpServerBuilder WithTool(Delegate tool, JsonSerializerOptions? options = null)
=> WithTool(builder, null!, null!, tool, options);

/// <summary>
/// Registers a specific method as a server tool.
/// </summary>
/// <param name="name">The name of the tool.</param>
/// <remarks>
/// The tool description will be set to the <see cref="DescriptionAttribute"/> on the method, if any.
/// </remarks>
public IMcpServerBuilder WithTool(string name, Delegate tool, JsonSerializerOptions? options = null)
=> WithTool(builder, name, null!, null!, tool, options);

/// <summary>
/// Registers a specific method as a server tool.
/// </summary>
/// <param name="name">The name of the tool.</param>
/// <param name="title">A human-readable title for the tool that can be displayed to users.</param>
/// <remarks>
/// The tool description will be set to the <see cref="DescriptionAttribute"/> on the method, if any.
/// </remarks>
public IMcpServerBuilder WithTool(string name, string title, Delegate tool, JsonSerializerOptions? options = null)
=> WithTool(builder, name, title, null!, tool, options);

/// <summary>
/// Registers a specific method as a server tool.
/// </summary>
/// <param name="name">The name of the tool.</param>
/// <param name="title">A human-readable title for the tool that can be displayed to users.</param>
/// <param name="description">The tool description.</param>
public IMcpServerBuilder WithTool(string name, string title, string description, Delegate tool, JsonSerializerOptions? options = null)
{
builder.Services.AddSingleton(services
=> McpServerTool.Create(tool, new() { Services = services, SerializerOptions = options }));
=> McpServerTool.Create(tool, new()
{
Name = name,
Title = title,
Description = description,
Services = services,
SerializerOptions = options
}));

return builder;
}
Expand Down