MSBuild properties
Every MSBuild property, item and target Snail.Sdk offers a project, per level, with its default and what it does; and
the properties the SDK sets on its own — which a repository may change and which it may not, and why. A name that
starts with _ is the SDK's own and may change in any release; everything on this page is public and changes only
as the versioning policy says.
All public names start with Snail. MSBuild has one flat namespace of properties shared by Microsoft's SDKs, NuGet,
every package's targets and the project itself, so a prefix is the only way to keep a name from meaning something
else elsewhere; Snail is that prefix for every level and stays so.
The levels
A project names one level in its Sdk attribute; each level includes the one before it.
| Level |
For |
Adds to the one before |
Snail.Sdk |
any project |
the rules of the standard, the analyzers, the project kinds, the conformance check |
Snail.Sdk.Library |
a library on NuGet |
public API files, package validation, native compatibility, symbols, the API reference, the site |
Snail.Sdk.Mcp |
an MCP server and its tests |
the server as a dotnet tool, Native AOT, transports, the tool reference, the install, the site |
<Project Sdk="Snail.Sdk.Library/0.1.0">
A project on another Microsoft SDK (Razor, Web) keeps it in the attribute and adds the level as an element,
<Sdk Name="Snail.Sdk.Mcp" />. The version usually lives once in global.json under msbuild-sdks.
Snail.Sdk
Properties a project or Directory.Build.props sets
| Property |
Default |
Effect |
SnailProjectKind |
from the name: Tests for *.Tests, Benchmarks for *.Benchmarks, NativeProbe for *.NativeProbe; otherwise empty |
The kind of project. Tool is set by hand: a program of the repository's own, such as a generator. See the kinds. |
SnailAnalyzers |
(true) |
false leaves out the implicit Snail.Sdk.Analyzers reference. |
SnailEveryRuleAsAdvice |
(true) |
false leaves out the generated config that lists every rule of the SDK's analysis level as a suggestion. |
SnailThreadingAnalyzersVersion |
17.14.15 |
The version of Microsoft.VisualStudio.Threading.Analyzers every project references. |
SnailConformanceRelease |
(false) |
true with -t:SnailConformance adds the release checks (S3LC0026). The release workflows pass it. |
Read, never set
| Property |
Value |
SnailSdkVersion |
The release of Snail.Sdk the project builds with, written into the package. The levels reference Snail.Sdk.Analyzers and the Snail.Sdk.Mcp.* libraries of this same version. |
Targets
| Target |
Run |
Does |
SnailConformance |
dotnet msbuild <a project> -t:SnailConformance |
Runs the conformance check on the repository the project is in (the folder of its global.json); an unmet required check is an MSBuild error carrying its id, advice a warning. Fails with a message when there is no global.json above the project. |
SnailAddEveryRuleAsAdvice |
before every compile |
Reads the SDK's listing of every rule of its analysis level, makes each warning or error a suggestion and gives the compiler that config between the SDK's own (level −100) and Snail.Sdk.globalconfig (level 100). A newer SDK band brings its new rules as advice with no file edited. |
Defaults and rules
The SDK sets two kinds of property. A default is set only when nothing has set it yet, so a repository's
Directory.Build.props (read before the SDK's props) or a project may change it. A rule of the standard is set
whatever Directory.Build.props says, because a whole repository must not opt out of the standard in one line; a
project body, which comes after, can still change it, and such a line is migration debt the
conformance check and a review see.
| Property |
Value |
Kind |
Why |
LangVersion |
latest |
default |
the language the SDK band ships |
ImplicitUsings |
enable |
default |
|
Authors |
orldev |
default |
|
PackageLicenseExpression |
MIT |
default |
|
Nullable |
enable |
rule |
nullable references are part of every API; the reference prints the annotations |
GenerateDocumentationFile |
true |
rule |
XML docs on every public member (CS1591), and the reference is made from them |
TreatWarningsAsErrors |
true |
rule |
every agreed rule fails the build only through it |
EnforceCodeStyleInBuild |
true |
rule |
code style is checked in the build, not only in an editor |
AnalysisLevel |
latest |
rule |
the analyzers of the SDK band global.json pins |
AnalysisMode |
Default |
rule |
Snail.Sdk.globalconfig is calibrated to it: Recommended would give some 145 rules a warning each, and the first rule a newer SDK added would fail the build |
NuGetAudit, NuGetAuditMode, NuGetAuditLevel |
true, all, low |
rule |
a known vulnerability in any package, direct or transitive, fails the build |
Deterministic |
true |
rule |
the same sources give the same binaries |
ContinuousIntegrationBuild |
true on CI (GITHUB_ACTIONS or CI is true) |
rule |
a package maps the paths it was compiled from to the repository's, so no machine's folder is written into it |
PublishRepositoryUrl, EmbedUntrackedSources, RepositoryType |
true, true, git |
rule |
a package says which commit it was built from (SourceLink, with the .NET SDK) |
To live with one vulnerability advisory for a while, name it rather than lower the level for every package:
<NuGetAuditSuppress Include="<advisory URL>" />.
The SDK also adds, to every project: Snail.Sdk.globalconfig (the agreed rules as warnings), a reference to
Snail.Sdk.Analyzers of the same release (the rules), and a reference to
Microsoft.VisualStudio.Threading.Analyzers. All are PrivateAssets="all": nothing reaches a package's users.
Project kinds
The kind decides what a project is spared and what it is. A kind is read from the name before any level's targets, so a
test project needs no lines of its own; a project whose name says otherwise sets SnailProjectKind in its body.
| Kind |
Named |
Output |
Packed |
Spared |
| (none) |
anything else |
as the project says |
yes |
nothing |
Tests |
*.Tests |
as the test SDK says |
no |
CS1591, CA1707, the Async suffix, CA1001 (Snail.Sdk.Tests.globalconfig); its strings are data to S3L0005 |
Benchmarks |
*.Benchmarks |
Exe |
no |
as Tests |
NativeProbe |
*.NativeProbe |
Exe, PublishAot, InvariantGlobalization |
no |
CS1591, CA1707 |
Tool |
never: set SnailProjectKind |
Exe |
no |
nothing — a tool is held to every rule of the code beside it |
A test project that sits directly in tests/ leaves out *.NativeProbe/**, so a probe beside it is not compiled
into it. A probe on Microsoft.NET.Sdk turns off SignalR's custom awaitables, as the Web SDK does for a native app. The
Library and Mcp levels act only on a project of no kind: a test or benchmark gets none of a library's or server's lines.
Snail.Sdk.Library
Properties a project or Directory.Build.props sets
| Property |
Default |
Effect |
SnailPackageBaseline |
(empty) |
The last released version. Becomes PackageValidationBaselineVersion, so a breaking change to the package fails the build, and the VersionPrefix of a build that names no version, so a local pack is never below the baseline. Set in Directory.Build.props once released (S3LC0019). |
SnailNotNativeBecause |
(empty) |
Why the library cannot work under Native AOT, in words. Set, IsAotCompatible is false and no *.NativeProbe is asked for (S3LC0020); empty, the library is native-compatible. |
SnailPublicApiAnalyzersVersion |
5.6.0 |
The version of Microsoft.CodeAnalysis.PublicApiAnalyzers. |
SnailReference |
(false) |
true (-p:SnailReference=true) writes the API reference after the build. |
SnailReferenceFolder |
docs/reference beside global.json (the project folder's docs/reference without one) |
The folder the reference pages are written into: <AssemblyName>.md. A trailing separator is accepted. |
What it sets
| Property or item |
Value |
Why |
EnablePackageValidation |
true |
a package is checked against the last release before it can break its users |
IsAotCompatible |
true, unless SnailNotNativeBecause |
the trim and AOT analyzers run on every library; beside the Razor or Web SDK they are turned on as Microsoft.NET.Sdk would |
IncludeSymbols, SymbolPackageFormat |
true, snupkg (default) |
a package carries its symbols; not for a package with no assembly (IncludeBuildOutput false), which NuGet refuses (NU5017) |
Snail.Sdk.Library.globalconfig |
RS0016, RS0017, RS0022, RS0024–RS0027, RS0036, RS0037 as warnings |
every public symbol is written down in PublicAPI.Shipped.txt/PublicAPI.Unshipped.txt, so a change to the API shows in the diff |
Microsoft.CodeAnalysis.PublicApiAnalyzers |
SnailPublicApiAnalyzersVersion |
reads those files |
Targets
| Target |
Run |
Does |
SnailReference |
after the build, with -p:SnailReference=true |
Writes <AssemblyName>.md into SnailReferenceFolder from the assembly just built and its XML docs, read without being run through the references the compiler used. CI fails when the committed page differs. |
SnailSite |
dotnet msbuild <project> -t:SnailSite |
Builds the site. See the site. |
Snail.Sdk.Mcp
Properties a project or Directory.Build.props sets
| Property |
Default |
Effect |
SnailMcpNative |
true |
Published with Native AOT, a tool package per platform. false for code loaded at run time (Roslyn, MSBuild, Razor, Playwright). |
SnailMcpTransport |
Stdio |
Stdio, Http or Both. Http and Both bring Snail.Sdk.Mcp.Http and a launchd job. |
SnailMcpContainer |
false |
true: an image is built from the repository (S3LC0025); a publish with no runtime stays framework-dependent, as a Dockerfile needs; the packages stay native. |
SnailMcpName |
PackageId, else AssemblyName, else the project name |
The server's name: its folder under SnailMcpRoot. |
SnailMcpShortName |
SnailMcpName without a leading Snail.MCP. (any case), without dots, lower case: Snail.MCP.Code.Context is codecontext |
Gives the command s3l-mcp-<short>, the launchd label s3l.mcp.<short>, the name a client sees in initialize. |
SnailReference |
(false) |
true writes the tool reference after the build. |
SnailReferenceFolder |
docs/reference beside global.json |
The folder the tool pages are written into: tools/index.md, a page per family, schemas/tools.json. A trailing separator is accepted. |
SnailReferenceSettings |
docs/tools.json beside global.json |
The families of tools and the words of the index page. |
InvariantGlobalization |
true (default) |
A server answers the same on every machine, and the native program carries no ICU. |
RollForward |
LatestMajor (default) |
A framework-dependent server runs on any newer runtime installed. |
ServerGarbageCollection |
false, under the Web SDK too |
The workstation GC: one client at a time needs none of the server GC's heaps per core. Measured on Snail.MCP.Roslyn with a solution loaded: 283 MB at idle, 481 MB with the server GC. The Web SDK's true is replaced, so a server that wants the server GC sets it in its project file. ConcurrentGarbageCollection stays the runtime's true: without it the same server sat at 355 MB. |
TieredPGO |
false when SnailMcpNative is false |
A framework-dependent server runs through the JIT without profile-guided tiering. Measured: Snail.MCP.Roslyn 281 → 259 MB at rest and a quarter less CPU, Snail.MCP.CodeContext 793 → 759 MB and a faster index; the light servers unchanged and none slower. A native server has no JIT. A server that wants it sets TieredPGO in its project file. |
Install properties
The install publishes a server into ~/.s3l-mcp/<SnailMcpName>/<date>-<time>.<commit>/ and points current at it
(Installing): dotnet publish src/<Server> -c Release -r osx-arm64 -p:SnailMcpInstall=true.
| Property or item |
Default |
Effect |
SnailMcpInstall |
(false) |
true installs on publish. -p:Install=true is the same request. |
SnailMcpRoot |
$(HOME)/.s3l-mcp |
The folder of every installed server. |
SnailMcpKept |
3 |
How many publications are kept besides current and any a running process uses. |
SnailMcpLaunchAgent |
deploy/s3l.mcp.<short>.plist when it exists and the transport is not Stdio |
The launchd job template, with {{HOME}}, {{CURRENT}} and {{DOTNET}} for this machine's paths; written beside the publications, never loaded by the build. |
SnailMcpWarmUp |
(empty) |
Arguments of the warm-up run of a native program after the install, so macOS inspects a new binary now rather than when a session starts it. |
item SnailMcpCompanion |
(none) |
Projects published into the same folder beside the server, such as a hook program. |
What it sets
| Property or item |
Value |
Why |
OutputType |
Exe |
a server is a program |
PackAsTool, PackageType |
true, McpServer |
the server is a dotnet tool of the MCP kind |
ToolCommandName |
s3l-mcp-<short> (default) |
one command name rule for every server; also written into the assembly as Snail.Sdk.Mcp.ServerName, the name ServerIdentity reports in initialize |
IsAotCompatible |
SnailMcpNative (default) |
|
CopyOutputSymbolsToPublishDirectory, PublishDocumentationFile, PublishReferencesDocumentationFiles |
false |
a publication carries the program, not its symbols or API docs |
PublishAot |
true when native, unless a container build publishes with no runtime |
|
OptimizationPreference |
Size (default, native) |
the same speed, a smaller program |
EnableConfigurationBindingGenerator |
true (default, native) |
settings are bound by generated code in every build alike |
EnableRequestDelegateGenerator |
true (default, native, not Stdio) |
HTTP endpoints bound by generated code too |
ToolPackageRuntimeIdentifiers |
osx-arm64;linux-x64;linux-arm64;win-x64;any (default, native) |
a tool package per platform and a framework-dependent one, any, for the rest; the workflows build exactly these |
PackageReadmeFile |
README.md when the repository has one |
the README and .mcp/server.json are packed |
| package references |
Snail.Sdk.Mcp.Hosting (server), Snail.Sdk.Mcp.Http (server, not Stdio), Snail.Sdk.Mcp.Testing (Tests), all of SnailSdkVersion |
the libraries of the same release |
Targets
| Target |
Run |
Does |
SnailReference |
after the build, with -p:SnailReference=true |
Writes the tool reference into SnailReferenceFolder from the server's own copy of the MCP SDK: the pages are what a client receives in tools/list; no tool runs. docs/tools.json is validated first (errors S3LD0001–S3LD0003, see Conformance). |
SnailMcpNamePublication |
before publish, on an install |
Names the publication folder (time and commit, -dirty with uncommitted changes) and publishes into it; never over an existing one. |
SnailMcpInstall |
after publish, on an install |
Publishes the companions, warms a native program up, points current at the publication, removes the old ones beyond SnailMcpKept, then writes the launchd job and prints or runs what loads it. |
SnailSite |
dotnet msbuild <project> -t:SnailSite |
Builds the site. |
The site (both levels)
dotnet msbuild <the library or server project> -t:SnailSite builds the site into obj/snail-site/site, strictly,
with the theme the SDK carries; -p:SnailSiteServe=true serves it, reloading on a change. A repository with
docs/mkdocs.yml keeps its navigation and pages and gets the theme; any other gets README.md as its home page, the
pages of docs/ and the reference last. MkDocs runs from a virtual environment in obj/snail-site/venv with the
versions the SDK pins, made on the first build of each SDK version and again when its Python is gone.
| Property |
Default |
Effect |
SnailSiteServe |
(false) |
true serves the site instead of building it. |
SnailSiteFolder |
obj/snail-site/site |
Where the site is built. |
SnailSiteName |
the README's first heading, else the repository's folder |
The site's name, when there is no docs/mkdocs.yml. |
SnailSiteDescription |
Description |
One sentence for every page. |
SnailSiteRepositoryUrl |
RepositoryUrl |
The repository on GitHub: links from a page to a file outside docs/ go there. |
SnailSiteLicense |
PackageLicenseExpression |
The licence the footer names. |
SnailSitePython |
(empty) |
An interpreter that already has the packages of the SDK's requirements.txt; empty, the build makes its own environment with python3 (python on Windows). |
| Property |
Read, never set |
SnailSiteTheme |
The theme folder, for a server whose own HTTP pages wear the theme (its css/site.css). |
SnailSiteProject |
true for a project of no kind: docs.yml builds the site on the first project under src/ that says so. SnailSite on a project of any kind fails. |
The site needs Python 3 with its venv module (on Debian and Ubuntu, python3-venv) and the package index on the
first build of each SDK version. The errors say what to do: SnailSitePython names <path>, which does not exist,
SnailSite makes MkDocs a virtual environment with python3, which could not make one, and
pip could not install the packages of <requirements.txt> each name the fix — install Python with venv, reach the
index, or set SnailSitePython.