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