Getting started¶
How to start a new Snail library, MCP server or ASP.NET application from the templates, how to bring an existing repository onto Snail.Sdk, what to do on a project's first day, and how to run locally everything the shared CI runs. A project made from a template builds without a warning, passes its tests and the conformance check, and — a server — answers its contract as a native program, before a line of it is changed.
Prerequisites¶
- The .NET SDK of the band in the template's
global.json(10.0.3xx):rollForward: latestPatchrefuses any other band, because the analyzers that judge the code ship with the SDK. - For a native server or a library's native probe: the platform's Native AOT toolchain (Xcode's command line tools on
macOS,
clangandzlib1g-devon Linux). - For the site: Python 3 with its
venvmodule (on Debian and Ubuntu,python3-venv).
A new project¶
dotnet new install Snail.Sdk.Templates
dotnet new s3l-lib -n Snail.Toolkit.Foo
dotnet new s3l-mcp -n Snail.MCP.Foo
dotnet new s3l-web -n Vertical.Foo
The templates build on the Snail.Sdk of their own release: global.json names it in msbuild-sdks. The project's name
is the repository's and the package's; for a server, the short name (foo) names its tools, its
command s3l-mcp-foo, its variables SNAIL_MCP_FOO_*, the contract variable SNAIL_FOO_SERVER and its launchd job.
| Template | Option | Default | Meaning |
|---|---|---|---|
| all | --description |
a placeholder sentence | the package description, the README's first paragraph, a server's instructions |
s3l-mcp |
--native |
true |
published with Native AOT (SnailMcpNative); false for a server that loads code at run time |
s3l-mcp |
--transport |
Stdio |
Stdio: a process per session; Http: one server for every session of the machine, over loopback HTTP, with a launchd job in deploy/; Both: stdio, or HTTP when SNAIL_MCP_<SHORT>_TRANSPORT=http |
s3l-mcp |
--container |
false |
true adds a Dockerfile (native or framework-dependent as --native says), .dockerignore and image.yml |
s3l-mcp |
--port |
9300 |
the loopback port of the HTTP endpoint |
s3l-web |
--kind |
api |
api: a minimal API; blazor: Blazor Server pages styled with Tailwind |
s3l-web |
--auth |
none |
cookie: a sign-in cookie, its keys on a volume; jwt: a bearer token through Snail.Toolkit.Authentication.JwtBearer |
s3l-web |
--mongo, --rabbit, --vault |
false |
MongoDB, RabbitMQ, and configuration from HashiCorp Vault, through their Snail.Toolkit packages |
s3l-web |
--localization |
false |
every sentence a person reads in Resources/Messages.resx, English, with Messages.ru.resx and Messages.es.resx beside it; a language is added by adding its file, and a request is answered in the language chosen (a cookie), then the browser's, then English (the standard) |
s3l-web |
--deploy |
compose |
compose: docker compose behind traefik; nomad: a Nomad job behind traefik |
s3l-web |
--port |
5080 |
the local port in development |
dotnet new s3l-mcp -n Snail.MCP.Foo --native false --transport Both --port 9310 --description "Lets an agent …"
What each template writes: the repository layout.
Templates of a given release¶
dotnet new install Snail.Sdk.Templates takes the latest release; dotnet new install Snail.Sdk.Templates::0.2.0 a
given one. The template's nuget.config maps every package to nuget.org, and nothing else is needed. A version of
Snail.Sdk not yet released comes from a local feed: contributing.
An existing repository¶
A repository written before Snail.Sdk moves onto it in one change: the files a template writes, the SDK in each project,
and then whatever the analyzers and the conformance check find in the code. Make a project of the same kind from the
template beside it first, dotnet new s3l-web -n Vertical.Foo -o ~/scratch/Vertical.Foo — its files are the ones to
copy, and the steps below name them.
- The root files. Copy
global.json(itsmsbuild-sdksnames the Snail.Sdk release),.editorconfigas it is — the rules ship in the SDK, and a rule kept here would outrank it — andLICENSE. Innuget.config, keep the repository's own sources and add apackageSourceMappingfor them. Copy.github/dependabot.ymland the workflows that call the shared ones:ci.yml,perf.yml, andpublish.ymlfor a package orimage.ymlfor an image. Directory.Build.props. Take out what the SDK sets —LangVersion,Nullable,ImplicitUsings,TreatWarningsAsErrors,EnforceCodeStyleInBuild,GenerateDocumentationFile,AnalysisLevel, anyNoWarn. What stays is the repository's own:Product, the target framework, central package management, a version prefix. The conformance check names a line left behind (S3LC0017).- The projects. A library builds on
Sdk="Snail.Sdk.Library", a server onSdk="Snail.Sdk.Mcp", an application keepsSdk="Microsoft.NET.Sdk.Web"and adds<Sdk Name="Snail.Sdk" />inside the project. A test project builds onSdk="Snail.Sdk", and loses its<GenerateDocumentationFile>false</GenerateDocumentationFile>: the SDK already asks no documentation of tests, and with the file switched off the build refuses to run IDE0005 and fails. - The build.
dotnet build -c Release --no-incremental— an incremental build may skip the compiler, and with it the new rules. What it reports is the code's own debt; the rules say how each is fixed. Two are common in code written before:usingdirectives no longer needed (IDE0005, found only once documentation files are on), and text in another language than English (S3L0005) — a prompt moves into a type named for it, and what a person reads into resources (the standard). - What a kind asks. A library: its public API in
PublicAPI.Shipped.txtas last released andSnailPackageBaselinenaming that release, or both empty before the first one. A server:.mcp/server.jsonanddocs/tools.json. An image:ARG VERSIONwith-p:Version=$VERSIONondotnet publish(S3LC0030), and tini as process 1, as the template'sDockerfilewrites them. - Benchmarks. A benchmark project in
benchmarks/with its first reports inbaseline/(S3LC0009, S3LC0010). - The check.
dotnet test -c Release, thendotnet msbuild src/<Project>.csproj -t:SnailConformanceuntil every line holds, then one commit.
The first day¶
- Git:
git init -b master, a first commit of everything the template wrote, and the repository on GitHub under the project's name (orldev/Snail.MCP.Foo); the template'sRepositoryUrlalready names it. - Your code: replace the sample (
Greetingof a library, thefoo_diagnosetool and its family indocs/tools.jsonof a server) with the first real type or tool. - The public API of a library: build, and add each public symbol the build reports (RS0016) to
src/PublicAPI.Unshipped.txt. It moves toPublicAPI.Shipped.txtat the first release. - The reference:
dotnet build -c Release -p:SnailReference=true, and commitdocs/reference. The CI writes it again and fails when it differs. - The benchmark baseline: the template's
baseline/is a placeholder measured elsewhere; replace it with the reports of the firstperf.ymlrun (below). - GitHub settings: Pages source GitHub Actions; default branch
master; for a container server, the image made public after its first push. Details: first-time setup. - NuGet: the secret
NUGET_USER=devorl, and a trusted publishing policy for the repository namingpublish.yml. Without them the release builds and checks everything and its push job fails.
Running everything locally¶
What the shared CI runs, on a developer's machine:
| What | Command |
|---|---|
| build and tests | dotnet test -c Release |
| conformance | dotnet msbuild src/<Project>.csproj -t:SnailConformance |
| conformance, as a release checks it | dotnet msbuild src/<Project>.csproj -t:SnailConformance -p:SnailConformanceRelease=true |
| the reference | dotnet build -c Release -p:SnailReference=true, then git status docs |
| the site | dotnet msbuild src/<Project>.csproj -t:SnailSite (into obj/snail-site/site) |
| the site, served and reloading | dotnet msbuild src/<Project>.csproj -t:SnailSite -p:SnailSiteServe=true |
| a library's native probe | dotnet publish tests/<Name>.NativeProbe -c Release -r osx-arm64 -o probe, then probe/<Name>.NativeProbe |
| a server's contract against its native build | dotnet publish src -c Release -r osx-arm64 -o out, then SNAIL_<SHORT>_SERVER=$PWD/out/<Name> dotnet test tests --filter "FullyQualifiedName~Contract" |
| benchmarks | dotnet run -c Release --project benchmarks/<Benchmarks project> -- --filter '*' --job short --exporters csv |
| install a server | dotnet publish src -c Release -r osx-arm64 -p:SnailMcpInstall=true (installing) |
The benchmark baseline is the CSV reports BenchmarkDotNet writes, *-report.csv, copied into baseline/ beside the
benchmark project when a change is accepted. perf.yml compares on its own runner (ubuntu-latest unless its os
input says otherwise), so the baseline comes from a run of perf.yml: download the run's benchmarks artifact and copy
its *-report.csv files. A local run shows the allocations, which do not depend on the machine, and the times, which do.