Skip to content

Commit 616c75a

Browse files
committed
Add latex MCP sample and screenshots
1 parent 01e588e commit 616c75a

File tree

8 files changed

+178
-2
lines changed

8 files changed

+178
-2
lines changed

assets/latex/dark.png

64.8 KB
Loading

assets/latex/light.png

57.9 KB
Loading

assets/latex/prefs.png

18.2 KB
Loading

assets/latex/prefs1.png

5.64 KB
Loading

assets/latex/prefs2.png

10.4 KB
Loading

assets/latex/prefsrender.png

31.9 KB
Loading

mcp/latex.cs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
MCP server that can turn LaTeX formatted equations into their markdown
3+
image representation for rendering inline in an editor that supports markdown
4+
rendering (i.e. VS Code).
5+
6+
> dotnet run latex.cs
7+
8+
Example mcp.json for VS Code:
9+
{
10+
"servers": {
11+
"latex": {
12+
"type": "stdio",
13+
"command": "dotnet",
14+
"args": [
15+
"run",
16+
"${workspaceFolder}${/}mcp${/}latex.cs"
17+
],
18+
// optionally hardcode preferences
19+
"env": {
20+
"LATEX__DARKMODE": "true",
21+
"LATEX__FONTSIZE": "tiny"
22+
}
23+
}
24+
}
25+
}
26+
27+
Showcases using elicitation for setting preferences:
28+
* #latex_setprefs: sets dark mode and font size preferences,
29+
* #latex_getprefs: reads the saved preferences.
30+
*/
31+
#:package Smith@0.2.5
32+
#:package DotNetConfig.Configuration@1.2.*
33+
#:package ModelContextProtocol@0.3.0-preview.*
34+
#:package Microsoft.Extensions.Http@9.*
35+
#:package SixLabors.ImageSharp@3.1.*
36+
37+
using ModelContextProtocol.Protocol;
38+
using ModelContextProtocol.Server;
39+
using SixLabors.ImageSharp;
40+
using SixLabors.ImageSharp.PixelFormats;
41+
42+
var builder = App.CreateBuilder(args);
43+
builder.Configuration.AddDotNetConfig();
44+
45+
var initialized = false;
46+
bool? darkMode = bool.TryParse(builder.Configuration["latex:darkMode"], out var dm) ? dm : null;
47+
string? fontSize = builder.Configuration["latex:fontSize"];
48+
// See https://editor.codecogs.com/docs/4-LaTeX_rendering.php#overview_anchor
49+
var fonts = new Dictionary<string, string>
50+
{
51+
{ "Tiny", "tiny" },
52+
{ "Small", "small" },
53+
{ "Large", "large" },
54+
{ "LARGE", "LARGE" },
55+
{ "Huge", "huge"}
56+
};
57+
58+
builder.Services
59+
.AddHttpClient()
60+
.AddMcpServer()
61+
.WithStdioServerTransport()
62+
.WithTool(
63+
name: "latex",
64+
title: "LaTeX to Image",
65+
description: "Converts LaTeX equations into markdown-formatted images for inline display.",
66+
tool: async (IHttpClientFactory httpFactory, IMcpServer server,
67+
[Description("The LaTeX equation to render.")] string latex)
68+
=>
69+
{
70+
// On first tool run, we ask for preferences for dark mode and font size.
71+
if (!initialized)
72+
{
73+
initialized = true;
74+
(darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize);
75+
}
76+
77+
var colors = darkMode switch
78+
{
79+
true => @"\fg{white}",
80+
false => @"\fg{black}",
81+
null => @"\bg{white}\fg{black}"
82+
};
83+
84+
var query = WebUtility.UrlEncode(@"\dpi{300}\" + (fontSize ?? "small") + colors + new string([.. latex.Where(c => !char.IsWhiteSpace(c))]));
85+
var url = $"https://latex.codecogs.com/png.image?{query}";
86+
87+
using var client = httpFactory.CreateClient();
88+
using var response = await client.GetAsync(url);
89+
response.EnsureSuccessStatusCode();
90+
91+
using var image = Image.Load<Rgba32>(await response.Content.ReadAsStreamAsync());
92+
using var ms = new MemoryStream();
93+
image.SaveAsPng(ms);
94+
var base64 = Convert.ToBase64String(ms.ToArray());
95+
return $"> ![LaTeX Equation](data:image/png;base64,{base64})";
96+
})
97+
.WithTool(
98+
name: "latex_getprefs",
99+
title: "Get LaTeX Preferences",
100+
description: "Gets the saved LaTeX rendering preferences for dark mode and font size.",
101+
tool: () => new { darkMode, fontSize },
102+
options: ToolJsonOptions.Default)
103+
.WithTool(
104+
name: "latex_setprefs",
105+
title: "Set LaTeX Preferences",
106+
description: "Sets the LaTeX rendering preferences for dark mode and font size.",
107+
tool: async (IMcpServer server,
108+
[Description("Use dark mode by inverting the colors in the output.")] bool? darkMode = null,
109+
[Description("Font size to use in the output: tiny=5pt, small=9pt, large=12pt, LARGE=18pt, huge=20pt")] string? fontSize = null)
110+
=> (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize),
111+
options: ToolJsonOptions.Default);
112+
113+
await builder.Build().RunAsync();
114+
115+
/// <summary>Saves the LaTeX rendering preferences to configuration.</summary>
116+
async ValueTask<(bool? darkMode, string? fontSize)> SetPreferences(IMcpServer server, bool? darkMode, string? fontSize)
117+
{
118+
if ((darkMode is null || fontSize is null || !fonts.ContainsValue(fontSize)) && server.ClientCapabilities?.Elicitation != null)
119+
{
120+
var result = await server.ElicitAsync(new()
121+
{
122+
Message = "Specify LaTeX rendering preferences",
123+
RequestedSchema = new()
124+
{
125+
Required = ["darkMode", "fontSize"],
126+
Properties =
127+
{
128+
{ "darkMode", new ElicitRequestParams.BooleanSchema()
129+
{
130+
Title = "Dark Mode",
131+
Description = "Use dark mode?",
132+
Default = darkMode
133+
}
134+
},
135+
{ "fontSize", new ElicitRequestParams.EnumSchema()
136+
{
137+
Title = "Font Size",
138+
Description = "Font size to use for the LaTeX rendering.",
139+
Enum = [.. fonts.Values],
140+
EnumNames = [.. fonts.Keys],
141+
}
142+
},
143+
},
144+
}
145+
});
146+
147+
if (result.Action == "accept" && result.Content is { } content)
148+
{
149+
darkMode = content["darkMode"].GetBoolean();
150+
fontSize = content["fontSize"].GetString() ?? "tiny";
151+
152+
DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global)
153+
.GetSection("latex")
154+
.SetBoolean("darkMode", darkMode.Value)
155+
.SetString("fontSize", fontSize);
156+
}
157+
// action == cancel is not supported in vscode
158+
// actoin == decline would be equal to "ignore" so we just don't set anything.
159+
return (darkMode, fontSize);
160+
}
161+
else
162+
{
163+
// We persist to ~/.netconfig
164+
var config = DotNetConfig.Config.Build(DotNetConfig.ConfigLevel.Global).GetSection("latex");
165+
if (darkMode != null)
166+
config = config.SetBoolean("darkMode", darkMode.Value);
167+
if (fontSize != null && fonts.ContainsValue(fontSize))
168+
config = config.SetString("fontSize", fontSize);
169+
else
170+
fontSize = null;
171+
172+
return (darkMode, fontSize);
173+
}
174+
}

src/MCPDemo/Program.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,17 @@
6565
name: "latex_getprefs",
6666
title: "Get LaTeX Preferences",
6767
description: "Gets the saved LaTeX rendering preferences for dark mode and font size.",
68-
tool: () => new { darkMode, fontSize })
68+
tool: () => new { darkMode, fontSize },
69+
options: ToolJsonOptions.Default)
6970
.WithTool(
7071
name: "latex_setprefs",
7172
title: "Set LaTeX Preferences",
7273
description: "Sets the LaTeX rendering preferences for dark mode and font size.",
7374
tool: async (IMcpServer server,
7475
[Description("Use dark mode by inverting the colors in the output.")] bool? darkMode = null,
7576
[Description("Font size to use in the output: tiny=5pt, small=9pt, large=12pt, LARGE=18pt, huge=20pt")] string? fontSize = null)
76-
=> (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize));
77+
=> (darkMode, fontSize) = await SetPreferences(server, darkMode, fontSize),
78+
options: ToolJsonOptions.Default);
7779

7880
await builder.Build().RunAsync();
7981

0 commit comments

Comments
 (0)