No projects just C# with `dotnet run app.cs` | DEM518

์ตœ์ƒ์œ„ ์‹คํ–‰๋ฌธ์ด ์ด์ œ์•ผ ์™„์„ฑ๋‹จ๊ณ„์— ์ด๋ฅธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

8๊ฐœ์˜ ์ข‹์•„์š”

์ด๊ฑฐ ์ข€ ์œ ์šฉํ•  ๊ฑฐ ๊ฐ™์€๋ฐ์š”.

agent mode๋‚˜ mcp๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ no project๋กœ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๊ฒŒ ์ข‹๋”๋ผ๊ณ ์š”.

5๊ฐœ์˜ ์ข‹์•„์š”

๋“œ๋””์–ด ํŒŒ์ด์ฌ๊ณผ ๋น„์Šทํ•œ ๊ฒฝํ—˜์„ ํ•  ์ˆ˜ ์žˆ๊ฒ ๊ตฌ๋‚˜! ์‹ถ์—ˆ์ง€๋งŒโ€ฆ ์•„๋ฌด๋ž˜๋„ ๋”œ๋ ˆ์ด๋ฅผ ๋ณด๋‹ˆ ์—ฌ์ „ํžˆ ์ปดํŒŒ์ผ ๊ณผ์ •์„ ๊ฑฐ์น˜๊ธด ํ•˜๋‚˜ ๋ด…๋‹ˆ๋‹ค.

5๊ฐœ์˜ ์ข‹์•„์š”

์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ์— ํ”„๋กœ์ ํŠธ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋˜ ๊ณผ๊ฑฐ์˜ Visual Studio Express Edition ์‹œ๋ฆฌ์ฆˆ์—์„œ ์ถœ๋ฐœํ•ด์„œ, ์ œ๊ฐ€ ์ง€๊ธˆ ์• ์šฉํ•˜๋Š” LINQPad๊นŒ์ง€ ์ƒ๊ฐํ•ด๋ณด๋ฉด ์ด๋Ÿฐ ad-hoc ์ฝ”๋”ฉ์„ ๋ฌด์ฒ™ ์ข‹์•„ํ–ˆ์—ˆ๋Š”๋ฐ, ๋“œ๋””์–ด ๋งˆ์นจํ‘œ๊ฐ€ ์ฐํžˆ๋Š” ๋А๋‚Œ์ด๋„ค์š”! ์„ธ์…˜ ์˜์ƒ์„ ๊ณต์œ ํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

4๊ฐœ์˜ ์ข‹์•„์š”

์˜ฌ๋ ค์ฃผ์‹  ๋‚ด์šฉ์ด ์ข€ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ๋ฌธ์„œํ™”๋œ ๊ฒƒ์ด ์žˆ๋‚˜ ํ•ด์„œ ์ฐพ์•„๋ณด๋‹ˆ ์—ญ์‹œ 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 ํ”„๋กœ์ ํŠธ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒƒ๊นŒ์ง€ ์™„๊ฒฐ์„ฑ์žˆ๊ฒŒ ์ฒ˜๋ฆฌ๋˜์–ด ์ œ๊ฐ€ ์•ž์—์„œ ์ด์•ผ๊ธฐํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์™„์„ฑ๋œ ํ”„๋กœ์ ํŠธ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ฒฝํ—˜๊นŒ์ง€ ๊ตฌํ˜„๋˜๋Š” ๊ฒƒ์ด์–ด์„œ ๋งˆ์Œ์— ์™ ๋“ญ๋‹ˆ๋‹ค. :smiley:

์•„์ง .NET 10์ด ํ”„๋ฆฌ๋ทฐ ๋ฒ„์ „์ด์ง€๋งŒ ๊ทธ๋Ÿผ์—๋„ ์ง€๊ธˆ๋ถ€ํ„ฐ ์‚ฌ์šฉํ•  ๋งŒํ•œ ๊ฐ•๋ ฅํ•œ ๋™์ธ์ด ์ €์—๊ฒŒ๋Š” ์ƒˆ๋กญ๊ฒŒ ์ƒ๊ธด๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ใ…Žใ…Ž

8๊ฐœ์˜ ์ข‹์•„์š”

MCP ๋•Œ๋ฌธ์— ํด๋กœ๋“œ ๋งฅ์Šค ๊ฒฐ์žฌํ–ˆ๋Š”๋ฐ ๋งค์šฐ ํ›Œ๋ฅญํ•œ ์ƒํƒœ๊ณ„๋”๋ผ๊ณ ์š”.

3๊ฐœ์˜ ์ข‹์•„์š”

Avalonia๋„ ๋‹น์—ฐํžˆ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ๋Š”๋ฐ ์•„์ฃผ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. :smiley:

์ด๋ ‡๊ฒŒ ๋งŒ๋“ค๋ฉด 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 { }
4๊ฐœ์˜ ์ข‹์•„์š”

magic-commands ๋ž‘์€ ๋˜ ๋‹ค๋ฅธ ์ „์ฒ˜๋ฆฌ์ž์˜ ํ™•์žฅ์ด์—ˆ๊ตฐ์š”

๋‹ท๋„ท10 ์ƒˆ๋กœ์šด๊ฒƒ ํ•˜๋‚˜ํ•˜๋‚˜๊ฐ€ ์—„์ฒญ ํŒŒ๊ฒฉ์ ์ด์˜ˆ์š” ๊ฑฑ์ •๋ฐ˜๊ธฐ๋Œ€๋ฐ˜โ€ฆ

image

๋ˆ„๊ฒŸ์— ํŒจํ‚ค์ง€๊ฐ€์ ธ๊ฐ€๋Š” ๋ฐฉ๋ฒ•๋งŒ 8๊ฐœ ์ ํ˜€์žˆ์„ ์ƒ๊ฐํ•˜๋‹ˆ ์–ด์งˆ์–ด์งˆํ•ฉ๋‹ˆ๋‹ค.

4๊ฐœ์˜ ์ข‹์•„์š”

์Šˆ๋ฑ…์ด REPL ํ™˜๊ฒฝ์—์„œ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์€ ์ฒ˜์Œ ์•Œ์•˜๋„ค์š”.

๋ฐœํ‘œ ์‹œ์ ์„ ๋ณด๋‹ˆ, ๋‹ท๋„ท ์ธํ„ฐ๋ ‰ํ‹ฐ๋ธŒ์—์„œ ์‚ฌ์šฉํ•˜๋˜ ๊ฒƒ์„ ์ข€ ๋” ํ™•์žฅํ•œ ๋“ฏ ๋ณด์ž…๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”