| name | cadenza |
| description | Cadenza is a .NET 10+ single-file scripting SDK family. Use it when a user wants a short C# script for shell automation, a background service, a Minimal API endpoint, an MCP server, or a local AI agent — each as a single `.cs` file that runs with `dotnet run` and publishes to a self-contained binary. |
Cadenza skill
Cadenza is .NET's answer to "Python for scripting" with "Go-like single-binary distribution". Every Cadenza program is one .cs file that runs with dotnet run app.cs and publishes to a ~30–40 MB self-contained binary with dotnet publish app.cs -r <rid> -c Release.
Repo: https://github.com/rkttu/cadenza
When to suggest Cadenza
Pick Cadenza when the user wants:
| Goal | SDK | Use case |
|---|
| CLI / shell automation / build glue | Cadenza | git deploy guards, file processing, devops scripts |
| Background service / daemon | Cadenza.Worker | heartbeat, polling, scheduled work |
| Minimal HTTP API / webhook | Cadenza.Web | a 30-line REST endpoint, internal tool |
| MCP server (AI tool integration) | Cadenza.Mcp | tools / resources / prompts for Claude Desktop, Cursor, VS Code AI |
| Local AI agent | Cadenza.Agent | OpenAI-compatible HTTP server fronting Ollama / OpenAI / Anthropic / Azure OpenAI — Codex / Aider / Continue / Cursor talk to it as if it were OpenAI |
Skip Cadenza for multi-project solutions, libraries that ship as DLLs, or anything that needs a full csproj. Cadenza is for the "single file = whole program" case.
Critical: exact version pinning
MSBuild SDK references do NOT support wildcards (1.*). Always pin an exact SemVer version. Check nuget.org for the latest, then write the exact version on the #:sdk line:
#:sdk Cadenza@1.0.15 // console
#:sdk Cadenza.Worker@1.0.15 // worker
#:sdk Cadenza.Web@1.0.15 // web
#:sdk Cadenza.Mcp@1.0.15 // MCP server
#:sdk Cadenza.Agent@1.0.15 // AI agent (OpenAI-compatible HTTP)
Latest versions (as of this skill update): all five SDKs at 1.0.15.
Tier 1 — bare names per variant (no namespace prefix needed)
Cadenza (console)
| Name | Signature | Purpose |
|---|
Run | int Run(string cmd, bool throwOnError = false) | Run a shell command, return exit code |
Capture | string Capture(string cmd) | Run a shell command, return stdout (throws on non-zero) |
ReadText | string ReadText(string path) | Read a UTF-8 text file |
WriteText | void WriteText(string path, string content) | Write a UTF-8 text file |
Glob | IEnumerable<string> Glob(string pattern) | Match files; supports ** and * |
TempDir | TempDirectory TempDir() | using var t = TempDir(); auto-cleanup |
WriteLine / Write / ReadLine | (standard System.Console) | stdout / stdin |
Cadenza.Worker
| Name | Signature | Purpose |
|---|
Run | Task Run(Func<CancellationToken, Task> work) | Start host + BackgroundService |
Config<T> | T Config<T>(string key) | IConfiguration shortcut (env vars, appsettings, etc.) |
| Shared from console | ReadText, WriteText, Glob, WriteLine | same as Cadenza |
Plus Log.Info(msg), Log.Warn(msg), Log.Error(msg, ex?), Log.Debug(msg) — routed through ILogger.
Cadenza.Web
| Name | Signature | Purpose |
|---|
Get / Post / Put / Delete / Map | (string path, Delegate handler) | Minimal API route map |
Run | Task Run() | Start the web server |
| Shared from console | ReadText, WriteText, Glob | same as Cadenza |
Web.App and Web.Services are the escape hatches when you need raw WebApplication / IServiceCollection.
Cadenza.Mcp
| Name | Signature | Purpose |
|---|
Tool | void Tool(string name, string description, Delegate handler) | Register an MCP tool the AI client can invoke |
Resource | void Resource(string uri, string name, Delegate handler) | Register an MCP resource |
Prompt | void Prompt(string name, string description, Delegate handler) | Register an MCP prompt template |
Run | Task Run() | Start the MCP server on stdio |
| Shared from console | ReadText, WriteText, Glob | same as Cadenza |
Plus Log.Info/Warn/Error/Debug routed to stderr (CRITICAL — see gotcha below).
Cadenza.Agent
| Name | Signature | Purpose |
|---|
Tool | void Tool(string name, string description, Delegate handler) | Register a callable tool. Parameter names/types become the JSON schema. |
SystemPrompt | void SystemPrompt(string text) | Override the default system prompt. |
UseOllama | void UseOllama(string model, string baseUrl = "http://localhost:11434") | Use a local Ollama daemon as the LLM. |
UseOpenAi | void UseOpenAi(string model, string? apiKey = null) | Use OpenAI (OPENAI_API_KEY fallback). |
UseAnthropic | void UseAnthropic(string model, string? apiKey = null) | Use Anthropic via its OpenAI-compatible endpoint (ANTHROPIC_API_KEY fallback). |
UseAzureOpenAi | void UseAzureOpenAi(string endpoint, string deployment, string? apiKey = null) | Use Azure OpenAI (AZURE_OPENAI_API_KEY fallback). |
UseChatClient | void UseChatClient(IChatClient client) | Plug in any custom IChatClient — UseFunctionInvocation() is wired automatically. |
Run | Task Run() | Start the OpenAI-compatible HTTP server (default localhost:8080). Exposes POST /v1/chat/completions (Aider / Continue / Cursor / Copilot BYOK), POST /v1/responses (Codex CLI — required since Feb 2026), GET /v1/models, GET /health. |
ChatLoop | Task ChatLoop() | Interactive console REPL — no HTTP server. |
Reply | Task<string> Reply(string prompt) | One-shot non-interactive call. |
Port, HostName, ServedModelName | properties | Configure the HTTP surface before calling Run(). |
| Shared from console | ReadText, WriteText, Glob, Capture, WriteLine | same as Cadenza |
Gotchas (read every one — they have bitten users)
-
MCP scripts must NEVER write to stdout. The stdio transport carries JSON-RPC on stdout; any stray text disconnects the client. The SDK intentionally does NOT expose WriteLine / Write / ReadLine as bare names in Cadenza.Mcp. Use Log.* (stderr) for diagnostics.
-
Wildcards in #:sdk don't work. Cadenza@1.* errors with "no version specified". Pin exact. To centralize version bumps, use global.json msbuild-sdks instead.
-
NativeAOT is opt-in. Default deployment is JIT-with-R2R, ~30–40 MB binary. To get a ~10–30 MB AOT binary, add #:property PublishAot=true at the top of the script. All deps must be AOT-compatible (Cadenza's own surface already is).
-
JSON requires JsonSerializerContext (source-generated). Http.GetJson<T>(url, ctx) and Json.Parse<T>(json, ctx) both take an explicit JsonSerializerContext so scripts stay AOT-clean. No reflection-based overloads exist.
-
Prompt.* in CI: set CADENZA_PROMPT_<NAME> env var (NAME = the question text uppercased, non-alphanumerics → _). Prompt.Password throws in CI without such an env var (no safe default).
-
The synthetic project's working directory is the directory holding the .cs file. Glob patterns and relative paths resolve from there.
-
Cadenza.Agent serves two wire formats over the same backend. POST /v1/chat/completions for Aider / Continue / Cursor / Copilot BYOK; POST /v1/responses for OpenAI Codex CLI (Codex removed Chat Completion support in Feb 2026 and now requires Responses). Server-side Tool(...) registrations are auto-invoked on the Chat path but NOT exposed on the Responses path — Codex sends its own toolset (shell, apply_patch, …) and runs them locally. So Cadenza-registered tools only show up for Chat-Completion clients. For Codex, treat Cadenza.Agent as a model adapter (pick the LLM, let Codex bring the tools).
Canonical patterns
Console: git-aware deploy gate
#!/usr/bin/env dotnet run
#:sdk Cadenza@1.0.15
var branch = Capture("git rev-parse --abbrev-ref HEAD").Trim();
if (branch != "main") { WriteLine($"Refusing to deploy from '{branch}'"); Env.Exit(1); }
if (Run("dotnet test", throwOnError: false) != 0) Env.Exit(2);
Run("dotnet publish -c Release -o ./dist", throwOnError: true);
Console: AOT-clean HTTP fetch with typed JSON
#!/usr/bin/env dotnet run
#:sdk Cadenza@1.0.15
using System.Text.Json.Serialization;
Http.Client.DefaultRequestHeaders.UserAgent.ParseAdd("my-script/1.0");
var repo = await Http.GetJson<Repo>("https://api.github.com/repos/dotnet/runtime", Ctx.Default);
WriteLine($"{repo.full_name}: {repo.stargazers_count:N0} stars");
record Repo(string full_name, int stargazers_count);
[JsonSerializable(typeof(Repo))]
partial class Ctx : JsonSerializerContext { }
Worker: periodic loop with graceful shutdown
#!/usr/bin/env dotnet run
#:sdk Cadenza.Worker@1.0.15
await Run(async (ct) =>
{
while (!ct.IsCancellationRequested)
{
Log.Info($"tick {DateTime.UtcNow:O}");
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
});
Web: Minimal API with record binding
#!/usr/bin/env dotnet run
#:sdk Cadenza.Web@1.0.15
Get("/", () => "hello");
Get("/health", () => new { status = "ok", time = DateTime.UtcNow });
Post("/echo", (EchoRequest req) => new EchoResponse(req.Message.ToUpper()));
await Run();
record EchoRequest(string Message);
record EchoResponse(string Echoed);
MCP server for Claude Desktop / Cursor / VS Code AI
#!/usr/bin/env dotnet run
#:sdk Cadenza.Mcp@1.0.15
Tool("read_file", "Read a UTF-8 text file from disk",
(string path) => ReadText(path));
Tool("list_files", "List files matching a glob pattern (e.g., **/*.cs)",
(string pattern) => Glob(pattern).ToArray());
await Run();
Register with the client:
{
"mcpServers": {
"cadenza-files": {
"command": "dotnet",
"args": ["run", "/absolute/path/to/server.cs"]
}
}
}
AI agent for Chat Completion clients (Aider / Continue / Cursor / Copilot BYOK)
#!/usr/bin/env dotnet run
#:sdk Cadenza.Agent@1.0.15
ServedModelName = "cadenza-coder";
SystemPrompt("You are a coding assistant. Ground answers in real files.");
Tool("read_file", "Read a UTF-8 text file from the working directory",
(string path) => ReadText(path));
Tool("list_files", "List files matching a glob pattern (e.g., src/**/*.cs)",
(string pattern) => Glob(pattern).ToArray());
UseOllama("qwen2.5-coder:7b");
await Run();
Point the editor at it:
export OPENAI_BASE_URL=http://localhost:8080/v1
export OPENAI_API_KEY=any-non-empty-string
aider
AI agent for Codex CLI (Responses API)
For Codex, drop tool registrations — Codex brings its own (shell, apply_patch, …). Cadenza.Agent acts as a pure model adapter:
#!/usr/bin/env dotnet run
#:sdk Cadenza.Agent@1.0.15
ServedModelName = "cadenza-codex";
UseOllama("qwen2.5-coder:7b");
await Run();
Configure Codex at ~/.codex/config.toml:
model_provider = "cadenza"
model = "cadenza-codex"
[model_providers.cadenza]
name = "Cadenza.Agent local"
base_url = "http://localhost:8080/v1"
wire_api = "responses"
env_key = "CADENZA_API_KEY"
stream_idle_timeout_ms = 300000
Then:
export CADENZA_API_KEY=any-non-empty-string
codex
Deployment
dotnet publish app.cs -r linux-x64 -c Release
dotnet publish app.cs -r linux-x64 -c Release
Supported RIDs: linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64, win-arm64.
Tier 2 — prefixed modules (shared by all variants)
Sh.Run/Capture/Pipe/RunAsync/CaptureAsync — shell exec with async variants
Fs.ReadText/WriteText/ReadBytes/WriteBytes/Exists/Delete/Move/Copy/MakeDir/Glob/TempDir/ReadTextAsync
Http.GetJson<T>/PostJson<TReq,TResp>/GetText/Download and Http.Client (shared HttpClient singleton)
Env.Get/Args/Cwd/Exit/IsCi/IsWindows/IsMacOS/IsLinux
Prompt.Confirm/Select/Text/Password (console + worker only)
Json.Parse<T>/Stringify<T> (always takes a JsonSerializerContext)
Reference