์ต์์ ์คํ๋ฌธ์ด ์ด์ ์ผ ์์ฑ๋จ๊ณ์ ์ด๋ฅธ ๊ฒ ๊ฐ์ต๋๋ค.
์ด๊ฑฐ ์ข ์ ์ฉํ ๊ฑฐ ๊ฐ์๋ฐ์.
agent mode๋ mcp๋ฅผ ์ฌ์ฉํ ๋ no project๋ก ์ฝ๋ ์์ฑํ๊ณ ์คํํ๋ ๊ฒ ์ข๋๋ผ๊ณ ์.
๋๋์ด ํ์ด์ฌ๊ณผ ๋น์ทํ ๊ฒฝํ์ ํ ์ ์๊ฒ ๊ตฌ๋! ์ถ์์ง๋งโฆ ์๋ฌด๋๋ ๋๋ ์ด๋ฅผ ๋ณด๋ ์ฌ์ ํ ์ปดํ์ผ ๊ณผ์ ์ ๊ฑฐ์น๊ธด ํ๋ ๋ด ๋๋ค.
์์ ๋๋ ํฐ๋ฆฌ์ ํ๋ก์ ํธ๋ฅผ ์ ์ฅํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๊ณผ๊ฑฐ์ Visual Studio Express Edition ์๋ฆฌ์ฆ์์ ์ถ๋ฐํด์, ์ ๊ฐ ์ง๊ธ ์ ์ฉํ๋ LINQPad๊น์ง ์๊ฐํด๋ณด๋ฉด ์ด๋ฐ ad-hoc ์ฝ๋ฉ์ ๋ฌด์ฒ ์ข์ํ์๋๋ฐ, ๋๋์ด ๋ง์นจํ๊ฐ ์ฐํ๋ ๋๋์ด๋ค์! ์ธ์ ์์์ ๊ณต์ ํด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!
์ฌ๋ ค์ฃผ์ ๋ด์ฉ์ด ์ข ๋ ๊ตฌ์ฒด์ ์ผ๋ก ๋ฌธ์ํ๋ ๊ฒ์ด ์๋ ํด์ ์ฐพ์๋ณด๋ ์ญ์ reddit์ ๊ตฌ์ฒด์ ์ธ ๋ด์ฉ์ด ์๋ค์!
https://www.reddit.com/r/rust/comments/1kwjmjm/dotnet_10_introduces_implicit_projects_with_a/
๊ทธ๋ฆฌ๊ณ ์ด ๊ธฐ๋ฅ์ implicit project (์์์ ํ๋ก์ ํธ)๋ผ๊ณ ๋ถ๋ฅด๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋ณธ๋ C#์์๋ '#'์ผ๋ก ์์ํ๋ ๊ฒ์ด ๋งคํฌ๋ก ์ ์ฒ๋ฆฌ๊ธฐ ๋ฐ์ ์์๋ ๊ฒ ๊ฐ์๋ฐ, ์ด ๊ธฐ๋ฅ์ ์ํด์ ๋ฌธ๋ฒ์ด ํ์ฅ๋ ๋ชจ์์ ๋๋ค. ํ์ผ์ ์์์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web
์ ์ผ ์ฒซ ์ค์ Shebang์ผ๋ก ๋ฐฐ์ ์ ธ ์คํฌ๋ฆฝํธ๋ ํ์์ ธ ์คํฌ๋ฆฝํธ ์ฒซ ์ค์ sh, bash, zsh๊ฐ ์ด ํ์ผ์ ์คํ ํ์ผ๋ก ๋ค๋ฃจ๋ ค ํ ๋ ํด๋น ์คํฌ๋ฆฝํธ๋ฅผ ์ด๋ ์ธํฐํ๋ฆฌํฐ์ ์ฐ๊ฒฐ์ํฌ์ง ๊ฒฐ์ ํ๋ ๋ถ๋ถ์ ๋๋ค. '#'์ ์ ์ฒ๋ฆฌ ๊ธฐํธ๋ก ์ด ๊ฒ์ ๊ทธ๋์ ๊ต์ฅํ ์๋ฆฌํ ์ ํ์ด๋ผ๋ ์๊ฐ์ด ๋ญ๋๋ค ใ ใ
์ด๋ฐ ํน์ฑ ๋๋ถ์ ์๋์ฒ๋ผ ๋ฆฌ๋ ์ค ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด .NET SDK 10 ๋ฒ์ ์ด ์ค์น๋์ด์๊ธฐ๋ง ํด๋ ๋ฐ๋ก ์ ธ ํ์ผ์ฒ๋ผ ๋ท๋ท ํ๋ก๊ทธ๋จ์ ์ธ ์ ์์ต๋๋ค.
chmod +x ./app.cs
./app.cs
๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ค์์ ์ค๋ ๋ผ์ธ๋ค์ CSPROJ์ ํต์ฌ ์ค์ ๋ค ์ค ์ค์ํ ์ค์ ๋ค์ ํธํ๊ฒ ์ง์ ํ ์ ์๊ฒ ๋์ด์์์ต๋๋ค. ๋๋ถ์ implicit project๋ฅผ ์ด์ฉํด์ ASP .NET Core Web API ์๋ฒ๋ ์์ฝ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
์ ๊ฐ ์์ฆ์ ASP .NET Core ๊ธฐ๋ฐ SSE MCP ์๋ฒ๋ฅผ ๋ง๋ค์ด๋ณด๊ณ ์๋ ์ฐธ์ด์ด์, LINQPad๋ก ๋์ด์๋ ์์ ๋ฅผ ๊ทธ๋๋ก ์ฎ๊ฒจ์ฌ ์ ์๋์ง ํ ์คํธํด๋ดค๋๋ฐ ์์ฃผ ํ๋ฅญํ๊ฒ ์ด์์ด ๋ฉ๋๋ค.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.Sdk.Web
#:package ModelContextProtocol@0.2.0-preview*
#:package ModelContextProtocol.AspNetCore@0.2.0-preview*
#:package OpenTelemetry.Exporter.OpenTelemetryProtocol@1.12.*
#:package OpenTelemetry.Extensions.Hosting@1.12.*
#:package OpenTelemetry.Instrumentation.AspNetCore@1.12.*
#:package OpenTelemetry.Instrumentation.Http@1.12.*
// Cloudflare๋ก tunnel์ ๋ง๋ค๊ธฐ ์ํด์ cloudflared๋ฅผ ์ฌ์ฉํ๋ฉด ํธ๋ฆฌํฉ๋๋ค.
// winget install --id Cloudflare.cloudflared
// cloudflared tunnel --url http://localhost:5000
#if LINQPAD
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
#endif
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Server;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using System.ComponentModel;
using System.Threading.Tasks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<EchoTool>()
.WithTools<EchoTool2>()
.WithTools<SampleLlmTool>();
builder.Services.AddOpenTelemetry()
.WithTracing(b => b.AddSource("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithMetrics(b => b.AddMeter("*")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation())
.WithLogging()
.UseOtlpExporter();
var app = builder.Build();
app.MapMcp();
app.Run();
[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the input back to the client.")]
public static string Echo(string message)
{
return "hello " + message;
}
}
[McpServerToolType]
public sealed class EchoTool2
{
[McpServerTool, Description("Echoes the input back to the client with Korean.")]
public static string Echo2(string message)
{
return "์๋
! " + message;
}
}
[McpServerToolType]
public sealed class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer thisServer,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)
{
ChatMessage[] messages =
[
new(ChatRole.System, "You are a helpful test server."),
new(ChatRole.User, prompt),
];
ChatOptions options = new()
{
MaxOutputTokens = maxTokens,
Temperature = 0.7f,
};
var samplingResponse = await thisServer.AsSamplingChatClient().GetResponseAsync(messages, options, cancellationToken);
return $"LLM sampling result: {samplingResponse}";
}
}
๊ทธ๋ฆฌ๊ณ ์ด๋ ๊ฒ ๋ง๋ ์ฒซ ํ์ผ์ dotnet project convert <app.cs ํ์ผ ๊ฒฝ๋ก>
๋ช
๋ น์ด๋ก ์คํํ๋ฉด Grow up ์ด๋ผ๋ ๊ธฐ๋ฅ์ด ํธ์ถ๋์ด ์ผ๋ฐ์ ์ธ .NET SDK ํ๋ก์ ํธ๋ก ์ ํํ๋ ๊ฒ๊น์ง ์๊ฒฐ์ฑ์๊ฒ ์ฒ๋ฆฌ๋์ด ์ ๊ฐ ์์์ ์ด์ผ๊ธฐํ๋ ๊ฒ์ฒ๋ผ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์์ ์์ํ์ฌ ์์ฑ๋ ํ๋ก์ ํธ๋ก ์ ํํ๋ ๊ฒฝํ๊น์ง ๊ตฌํ๋๋ ๊ฒ์ด์ด์ ๋ง์์ ์ ๋ญ๋๋ค.
์์ง .NET 10์ด ํ๋ฆฌ๋ทฐ ๋ฒ์ ์ด์ง๋ง ๊ทธ๋ผ์๋ ์ง๊ธ๋ถํฐ ์ฌ์ฉํ ๋งํ ๊ฐ๋ ฅํ ๋์ธ์ด ์ ์๊ฒ๋ ์๋กญ๊ฒ ์๊ธด๊ฒ ๊ฐ์ต๋๋ค. ใ ใ
MCP ๋๋ฌธ์ ํด๋ก๋ ๋งฅ์ค ๊ฒฐ์ฌํ๋๋ฐ ๋งค์ฐ ํ๋ฅญํ ์ํ๊ณ๋๋ผ๊ณ ์.
Avalonia๋ ๋น์ฐํ ํ
์คํธํด๋ณผ ์ ์์ ๊ฒ์ด๋ผ ์๊ฐํ๋๋ฐ ์์ฃผ ์ ์๋ํฉ๋๋ค.
์ด๋ ๊ฒ ๋ง๋ค๋ฉด GUI ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ฉด์, Kestrel์ ๋ฐฑ์๋๋ก ์ธ์ธ ์ ์๊ณ , ํฌ๋ก์ค ํ๋ซํผ์ ๊ณ ๋ คํด์ ๋ฆฌ๋ ์ค, macOS์์๋ ๋์ผํ GUI๋ฅผ ์ ๊ณตํ ์ ์์ง ์์๊น ๊ธฐ๋๊ฐ ๋ฉ๋๋ค.
#!/usr/bin/dotnet run
#:sdk Microsoft.NET.SDK.Web
#:package Avalonia.Desktop@11.*
#:package Avalonia.Fonts.Inter@11.*
#:package Avalonia.ReactiveUI@11.*
#:package Avalonia.Themes.Fluent@11.*
#:package Lemon.Hosting.AvaloniauiDesktop@1.*
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Lemon.Hosting.AvaloniauiDesktop;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using System.Runtime.InteropServices;
using Avalonia.Themes.Fluent;
#if LINQPAD
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
Util.NewProcess = true;
#endif
var builder = Host.CreateApplicationBuilder();
builder.Configuration
.AddCommandLine(args)
.AddEnvironmentVariables()
.AddInMemoryCollection();
builder.Logging
.AddConsole();
builder.Services.AddAvaloniauiDesktopApplication<App>(
b => b.UsePlatformDetect().WithInterFont().LogToTrace());
builder.Services.AddMainWindow<MainWindow, MainWindowViewModel>();
var app = builder.Build();
await app.RunAvaloniauiApplication<MainWindow>(args,
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ShutdownMode.OnExplicitShutdown : ShutdownMode.OnLastWindowClose);
public sealed class App : Application
{
public App(IServiceProvider sp)
=> this.Styles.Add(new FluentTheme(sp));
}
public sealed class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
Title = "No XAML UI";
Width = 400;
Height = 300;
var button = new Button
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
button.Bind(Button.ContentProperty, new Binding { Path = nameof(viewModel.ButtonCaption), });
button.Bind(Button.CommandProperty, new Binding { Path = nameof(viewModel.ButtonAction), });
Content = new Grid
{
Children =
{
button,
}
};
}
}
public sealed class MainWindowViewModel : ViewModelBase
{
private int _count;
private string _buttonCaption = "Hello";
public string ButtonCaption
{
get => _buttonCaption;
set => this.RaiseAndSetIfChanged(ref _buttonCaption, value);
}
public void ButtonAction() => ButtonCaption = $"Clicked {++_count} time(s)!";
}
public abstract class ViewModelBase : ReactiveObject { }
magic-commands ๋์ ๋ ๋ค๋ฅธ ์ ์ฒ๋ฆฌ์์ ํ์ฅ์ด์๊ตฐ์
๋ท๋ท10 ์๋ก์ด๊ฒ ํ๋ํ๋๊ฐ ์์ฒญ ํ๊ฒฉ์ ์ด์์ ๊ฑฑ์ ๋ฐ๊ธฐ๋๋ฐโฆ
๋๊ฒ์ ํจํค์ง๊ฐ์ ธ๊ฐ๋ ๋ฐฉ๋ฒ๋ง 8๊ฐ ์ ํ์์ ์๊ฐํ๋ ์ด์ง์ด์งํฉ๋๋ค.
์๋ฑ ์ด REPL ํ๊ฒฝ์์ ์ธ ์ ์๋ค๋ ์ ์ ์ฒ์ ์์๋ค์.
๋ฐํ ์์ ์ ๋ณด๋, ๋ท๋ท ์ธํฐ๋ ํฐ๋ธ์์ ์ฌ์ฉํ๋ ๊ฒ์ ์ข ๋ ํ์ฅํ ๋ฏ ๋ณด์ ๋๋ค.