diff --git a/src/dotnet/skills/nuget-trusted-publishing/SKILL.md b/src/dotnet/skills/nuget-trusted-publishing/SKILL.md
new file mode 100644
index 0000000000..ebca1abb46
--- /dev/null
+++ b/src/dotnet/skills/nuget-trusted-publishing/SKILL.md
@@ -0,0 +1,164 @@
+---
+name: nuget-trusted-publishing
+description: >
+ Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys
+ with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish,
+ migrate from NuGet API key, NuGet/login, secure NuGet publishing.
+ DO NOT USE FOR: publishing to private feeds or Azure Artifacts (OIDC is nuget.org only).
+ INVOKES: shell (powershell or bash), edit, create, ask_user for guided repo setup.
+---
+
+# NuGet Trusted Publishing Setup
+
+Set up [NuGet trusted publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing) on a GitHub Actions repo. Replaces long-lived API keys with OIDC-based short-lived tokens — no secrets to rotate or leak.
+
+## Prerequisites
+
+- **GitHub Actions** — this skill covers GitHub Actions setup specifically (trusted publishing also supports Azure DevOps, but that requires a different configuration flow)
+- **nuget.org account** — the user needs access to create trusted publishing policies
+
+## When to Use This Skill
+
+Use this skill when:
+- Setting up trusted publishing for a NuGet package
+- Migrating from `secrets.NUGET_API_KEY` to OIDC-based publishing
+- Asked about keyless or secure NuGet publishing
+- Creating a new NuGet publish workflow from scratch
+- Asked to "remove NuGet API key" or "use NuGet/login"
+- Setting up publishing for a dotnet tool, MCP server, or template package
+- Asked about `NuGet/login@v1` or `id-token: write`
+
+## Safety Rules
+
+> ⚠️ **Bail-out rule**: If any phase fails after one fix attempt on an infrastructure/auth issue, stop and ask the user. Don't loop on environment problems.
+
+> ⚠️ **Never delete or overwrite without confirmation**: Removing API key secrets, deleting tags/releases, removing workflow steps, or changing package IDs. NuGet package IDs are permanent — mistakes can't be undone.
+
+## Process
+
+> **Fast-path for greenfield repos**: When the user has a simple setup (one packable project, no existing publish workflow), don't gate on multi-turn assessment. Combine phases: create the workflow immediately, include nuget.org policy guidance, local pack recommendation, and filename-matching warning all in one response. The full phased process below is for complex or migration scenarios.
+
+### Phase 1: Assess
+
+Inspect the repo and report findings before making any changes.
+
+1. **Find and classify packable projects** — check `.csproj` files **and `Directory.Build.props`** (package metadata is often set repo-wide). Classify in this order (earlier matches win):
+ - `Template` → **Template**
+ - `McpServer` → **MCP server** (also a dotnet tool)
+ - `true` → **Dotnet tool**
+ - Class library (`IsPackable=true` or no `OutputType`) → **Library**
+ - `Exe` with `true` → **Application package** (not a tool, but still publishable)
+ - `Exe` without `PackAsTool` or `IsPackable` → Not packable by default (ask user if they intend to publish it)
+
+2. **Validate structure** for each project's type:
+
+ | Type | Required |
+ |------|----------|
+ | All | `PackageId`, `Version` (in .csproj or Directory.Build.props) |
+ | Dotnet tool | `PackAsTool` (required); `ToolCommandName` (optional but recommended — defaults to assembly name) |
+ | MCP server | `PackageType=McpServer`, `.mcp/server.json` included in package |
+ | Template | `PackageType=Template`, `.template.config/template.json` under content dir |
+
+3. **Find existing publish workflows** in `.github/workflows/` — look for `dotnet nuget push`, `nuget push`, or `dotnet pack`.
+
+4. **Check version consistency** — for MCP servers, verify `.csproj` `` matches both `server.json` version fields (root `version` and `packages[].version`). Flag any mismatch.
+
+5. **Report findings** to the user: classification, missing properties, version mismatches, existing workflows. For multi-project repos, note whether one workflow or separate workflows per package are needed. Offer to fix gaps — use `ask_user` before modifying project files.
+
+> ❌ See [references/package-types.md](references/package-types.md) for per-type details and required properties.
+
+### Phase 2: Local Verification
+
+Pack and verify locally before touching nuget.org — publishing errors waste a permanent version number.
+
+> ⚠️ **Always mention this step**, even if you defer running it. Tell the user: "Before your first publish, run `dotnet pack -c Release -o ./artifacts` to verify the .nupkg is created correctly."
+
+1. `dotnet pack -c Release -o ./artifacts` — verify `.nupkg` is created
+2. For tools/MCP servers: install from `./artifacts`, run `--help`, uninstall
+3. For libraries: inspect the `.nupkg` contents (it's a zip)
+
+### Phase 3: nuget.org Policy
+
+This phase requires the user to act on nuget.org — guide them with exact values.
+
+1. Determine the **repo owner**, **repo name**, and the **workflow filename** that will publish.
+
+ > ❌ The policy requires the **exact workflow filename** (e.g., `publish.yml` or `publish.yaml`) — just the filename, no path prefix. Matching is case-insensitive. Don't use the workflow `name:` field.
+
+2. Guide the user to create the trusted publishing policy:
+ > Go to [**nuget.org/account/trustedpublishing**](https://www.nuget.org/account/trustedpublishing) → **Add policy**
+ >
+ > - **Repository Owner**: `{owner}`
+ > - **Repository**: `{repo}`
+ > - **Workflow File**: `{filename}.yml`
+ > - **Environment**: `release` *(only if the workflow uses `environment:`; leave blank otherwise)*
+
+ Policy ownership: the user chooses individual account or organization. Org-owned policies apply to all packages owned by that org.
+
+ For **private repos**: policy is "temporarily active" for 7 days — becomes permanent after the first successful publish.
+
+3. Guide the user to create a **GitHub Environment** (recommended but optional — provides secret scoping + approval gates):
+ > Repo **Settings** → **Environments** → **New environment** → `release`
+ >
+ > Add environment secret: **Name** = `NUGET_USER`, **Value** = nuget.org username (NOT email)
+
+ Optional: add **Required reviewers** for an approval gate.
+
+> ⚠️ Wait for the user to confirm they've created the policy before proceeding.
+
+### Phase 4: Workflow Setup
+
+Create or modify the publish workflow. **The workflow must always be created or shown in your response** — don't defer this to a later turn.
+
+**Greenfield**: Create `publish.yml` from the template in [references/publish-workflow.md](references/publish-workflow.md). Adapt .NET version, project path, and environment name. Ensure your output explicitly mentions `id-token: write` and `NuGet/login@v1`.
+
+**Migration** (existing workflow with API key): Modify in place —
+
+1. **Add OIDC permission and environment** to the publishing job:
+ ```yaml
+ jobs:
+ publish:
+ environment: release
+ permissions:
+ id-token: write # Required — without this, NuGet/login fails with 403
+ contents: read # Explicit — setting permissions overrides defaults
+ ```
+
+2. **Add the NuGet login step** before push:
+ ```yaml
+ - name: NuGet login (OIDC)
+ id: login
+ uses: NuGet/login@v1
+ with:
+ user: ${{ secrets.NUGET_USER }} # nuget.org profile name, NOT email
+ ```
+
+3. **Replace the API key** in the push step:
+ ```yaml
+ --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicate
+ ```
+
+4. **Verify**: Ask the user to trigger a publish and confirm the package appears on nuget.org.
+
+> ❌ **Don't delete the old API key secret** until trusted publishing is verified. Removing it is a one-way door — wait for confirmation.
+
+## Troubleshooting
+
+| Problem | Cause | Fix |
+|---------|-------|-----|
+| `NuGet/login` 403 | Missing `id-token: write` | Add to job permissions |
+| "no matching policy" | Workflow filename mismatch | Verify exact filename on nuget.org |
+| Push unauthorized | Package not owned by policy account | Check policy owner on nuget.org |
+| Token expired | Login step >1hr before push | Move `NuGet/login` closer to push |
+| "temporarily active" policy | Private repo, first publish pending | Publish within 7 days |
+| `already_exists` on push | Re-running same version | Add `--skip-duplicate` |
+| GitHub Release 422 | Duplicate release for tag | Delete conflicting release (confirm first) |
+| Re-run uses wrong YAML | `gh run rerun` replays original commit's YAML | Delete obstacle, re-run — never re-tag |
+
+> ⚠️ If any blocker persists after one fix attempt, **stop and ask the user**.
+
+## References
+
+- **Package type details**: [references/package-types.md](references/package-types.md) — detection logic, required properties, minimal .csproj examples
+- **Publish workflow template**: [references/publish-workflow.md](references/publish-workflow.md) — complete tag-triggered workflow ready to adapt
+- **Microsoft docs**: [NuGet Trusted Publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing)
diff --git a/src/dotnet/skills/nuget-trusted-publishing/references/package-types.md b/src/dotnet/skills/nuget-trusted-publishing/references/package-types.md
new file mode 100644
index 0000000000..4c21bd6ef3
--- /dev/null
+++ b/src/dotnet/skills/nuget-trusted-publishing/references/package-types.md
@@ -0,0 +1,256 @@
+# NuGet Package Type Reference
+
+Structural requirements for each NuGet package type. The agent uses this to validate a repo's packaging setup before configuring trusted publishing.
+
+## Detection Logic
+
+Inspect `.csproj` files (and `Directory.Build.props` if present) for these MSBuild properties:
+
+```
+1. Has Template? → Template package
+2. Has McpServer? → MCP server (also a dotnet tool)
+3. Has true? → Dotnet tool
+4. Has true or no OutputType? → NuGet library
+5. Has Exe + true? → Application package
+6. Has Exe without PackAsTool or IsPackable? → Not packable by default (ask user)
+```
+
+Check in order — MCP servers have `PackAsTool` too, so `PackageType` must be checked first.
+
+## NuGet Library
+
+The most common case. A class library consumed via `PackageReference`.
+
+### Required Properties
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `PackageId` | `Contoso.Utilities` | Defaults to `AssemblyName` if omitted |
+| `Version` | `0.1.0` | Start with 0.x for initial development |
+
+### Recommended Properties
+
+| Property | Purpose |
+|----------|---------|
+| `Authors` | Package author(s) |
+| `Description` | Shown on nuget.org |
+| `PackageTags` | Discoverability |
+| `PackageReadmeFile` | README displayed on nuget.org |
+| `PackageLicenseExpression` | SPDX license identifier |
+| `RepositoryUrl` | Link back to source |
+| `PublishRepositoryUrl` | Enables source-link integration |
+
+### Including README in Package
+
+`PackageReadmeFile` alone isn't enough — you must also include the file in the package:
+
+```xml
+
+ README.md
+
+
+
+
+
+
+```
+
+### Minimal .csproj
+
+```xml
+
+
+ net9.0
+ Contoso.Utilities
+ 0.1.0
+ Contoso
+ Utility library for Contoso apps
+ README.md
+
+
+
+
+
+```
+
+### Pack Command
+
+```bash
+dotnet pack -c Release
+```
+
+## Dotnet Tool
+
+A console app distributed as a global or local tool via `dotnet tool install`.
+
+### Required Properties
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `OutputType` | `Exe` | Must be an executable |
+| `PackAsTool` | `true` | Marks this as a tool package |
+| `PackageId` | `contoso-cli` | Tool package identifier |
+| `Version` | `0.1.0` | Package version (or set in Directory.Build.props) |
+
+### Recommended Properties
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `ToolCommandName` | `contoso` | Command users type; defaults to assembly name |
+| `PackageOutputPath` | `./nupkg` | Where .nupkg is written |
+| `PackageReadmeFile` | `README.md` | Shown on nuget.org |
+
+### Minimal .csproj
+
+```xml
+
+
+ Exe
+ net9.0
+ true
+ contoso
+ contoso-cli
+ README.md
+
+
+
+
+
+```
+
+### Pack Command
+
+```bash
+dotnet pack -c Release
+```
+
+## MCP Server
+
+A dotnet tool that implements the Model Context Protocol. Distributed the same way as a dotnet tool but with additional metadata for MCP client discovery.
+
+### Naming Convention
+
+Follow the established pattern for MCP server packages:
+- **PackageId**: `{github-username}.{domain}.mcp` (e.g., `lewing.helix.mcp`)
+- **server.json `name`**: `io.github.{username}/{packageid}` (e.g., `io.github.lewing/lewing.helix.mcp`)
+
+### Required Properties
+
+Everything from Dotnet Tool, plus:
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `PackageType` | `McpServer` | NuGet recognizes this as an MCP server |
+
+### Recommended Properties
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `McpServerJsonTemplateFile` | `.mcp/server.json` | MCP client discovery metadata |
+
+### Required Files
+
+| File | Purpose |
+|------|---------|
+| `.mcp/server.json` | MCP server descriptor for client discovery |
+
+The `.mcp/server.json` must be included in the package:
+
+```xml
+
+
+
+```
+
+### Minimal server.json
+
+```json
+{
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
+ "name": "io.github.contoso/contoso.services.mcp",
+ "description": "MCP server for Contoso services",
+ "version": "0.1.0",
+ "packages": [
+ {
+ "registryType": "nuget",
+ "registryBaseUrl": "https://api.nuget.org",
+ "identifier": "contoso.services.mcp",
+ "version": "0.1.0",
+ "transport": { "type": "stdio" }
+ }
+ ]
+}
+```
+
+> ⚠️ **Version sync**: The `version` fields in `server.json` MUST match the `` in your `.csproj`. Update both when bumping versions.
+
+> ⚠️ **nuget.org MCP Server tab**: nuget.org auto-generates MCP install config from your `server.json`. If your tool requires a subcommand (e.g., `my-tool mcp`), the generated config may omit it. Ensure your tool defaults to MCP server mode when invoked with no arguments, or uses a `--yes` flag for non-interactive acceptance.
+
+### Minimal .csproj
+
+```xml
+
+
+ Exe
+ net9.0
+ true
+ McpServer
+ contoso.services.mcp
+ contoso-mcp
+ .mcp/server.json
+ README.md
+
+
+
+
+
+
+```
+
+## Template Package
+
+A package containing `dotnet new` templates.
+
+### Required Properties
+
+| Property | Example | Notes |
+|----------|---------|-------|
+| `PackageType` | `Template` | NuGet recognizes this as a template package |
+| `PackageId` | `Contoso.Templates` | Template package identifier |
+
+### Required Files
+
+| File | Purpose |
+|------|---------|
+| `content/*/.template.config/template.json` | Template definition — one per template |
+
+The `template.json` must include at minimum: `identity`, `name`, `shortName`, `tags.type` (`item` or `project`).
+
+### Minimal .csproj
+
+```xml
+
+
+ Template
+ Contoso.Templates
+ 0.1.0
+ Contoso project templates
+
+
+ true
+ false
+ content
+ true
+
+
+
+
+
+```
+
+## Common Gotchas
+
+- **`IsPackable` defaults**: Class libraries default to `true`, console apps to `false`. Console apps can still be published as NuGet packages by setting `true` — they just won't be installable via `dotnet tool install` unless `PackAsTool` is also set.
+- **`Directory.Build.props`**: Package metadata may be set at the repo root — always check there too.
+- **Multi-project repos**: A repo may contain multiple packable projects of different types. Each needs its own trusted publishing workflow or a matrix build.
+- **`GeneratePackageOnBuild`**: If `true`, `dotnet build` also produces the `.nupkg`. The workflow should use `dotnet pack` explicitly for clarity.
diff --git a/src/dotnet/skills/nuget-trusted-publishing/references/publish-workflow.md b/src/dotnet/skills/nuget-trusted-publishing/references/publish-workflow.md
new file mode 100644
index 0000000000..d74311c9fb
--- /dev/null
+++ b/src/dotnet/skills/nuget-trusted-publishing/references/publish-workflow.md
@@ -0,0 +1,102 @@
+# Publish Workflow Template
+
+Complete tag-triggered GitHub Actions workflow for publishing NuGet packages with trusted publishing. Copy and adapt to your repo.
+
+## Template
+
+```yaml
+name: Publish to NuGet
+
+on:
+ push:
+ tags:
+ - 'v*' # Triggers on version tags: v1.0.0, v1.2.3-preview.1, etc.
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ environment: release # Uses release environment for secret scoping + protection rules
+ permissions:
+ id-token: write # Required for OIDC token (NuGet trusted publishing)
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x' # Adjust to your target framework
+
+ - name: Extract version from tag
+ id: version
+ run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
+
+ - name: Validate version matches project
+ run: |
+ PROJECT_VERSION=$(sed -n 's:.*\(.*\).*:\1:p' path/to/YourProject.csproj)
+ if [ "$PROJECT_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then
+ echo "::error::Tag version (${{ steps.version.outputs.VERSION }}) doesn't match project version ($PROJECT_VERSION)"
+ exit 1
+ fi
+
+ - name: Pack
+ run: dotnet pack path/to/YourProject.csproj -c Release -o ./artifacts
+
+ - name: NuGet login (OIDC)
+ id: login
+ uses: NuGet/login@v1
+ with:
+ user: ${{ secrets.NUGET_USER }} # nuget.org profile name (NOT email)
+
+ - name: Push to NuGet
+ run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
+```
+
+## Customization Points
+
+| Item | What to change |
+|------|---------------|
+| `dotnet-version` | Match your `TargetFramework` |
+| `path/to/YourProject.csproj` | Path to your packable project |
+| Version extraction `sed` | Adjust for `Directory.Build.props` or .NET 10 file-based apps (`#:property Version=`) |
+| `--skip-duplicate` | Keeps push idempotent — safe for re-runs and matrix builds |
+
+## Release Process
+
+Once the workflow is committed, the publish process is:
+
+```bash
+# 1. Bump version in .csproj (and server.json for MCP servers)
+# 2. Commit
+git add -A && git commit -m "Bump version to 0.1.0"
+# 3. Tag and push
+git tag v0.1.0
+git push origin main --tags
+# 4. Workflow runs automatically, publishes to nuget.org
+```
+
+## Optional: GitHub Release Step
+
+Add after the push step if you want GitHub Releases with the `.nupkg` attached. Note: this requires changing `contents: read` to `contents: write` in the job permissions.
+
+```yaml
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
+ with:
+ files: ./artifacts/*.nupkg
+ generate_release_notes: true
+```
+
+> ⚠️ **Consider omitting this step entirely.** Creating GitHub Releases separately (manually or via `gh release create` in a different workflow) avoids 422 `already_exists` conflicts and keeps the publish workflow focused on NuGet. If any release step fails, it blocks NuGet publishing too.
+
+> ⚠️ **Don't use `ncipollo/release-action` AND `gh release create` for the same tag** — this causes HTTP 422 `already_exists` errors.
+
+> ⚠️ **`gh run rerun` replays the original YAML** from the tag commit, not from `main`. If the workflow fails due to a release conflict, delete the conflicting release and re-run — don't delete the tag and re-tag (NuGet package IDs are permanent).
+
+## CI vs Publish Separation
+
+Keep your CI workflow (build + test on PR/push) separate from the publish workflow (tag-triggered). This gives you:
+- CI runs on every PR without publishing
+- Publish only runs on deliberate version tags
+- Different permission scopes (CI doesn't need `id-token: write`)
diff --git a/src/dotnet/tests/nuget-trusted-publishing/eval.yaml b/src/dotnet/tests/nuget-trusted-publishing/eval.yaml
new file mode 100644
index 0000000000..8f44dd9d7c
--- /dev/null
+++ b/src/dotnet/tests/nuget-trusted-publishing/eval.yaml
@@ -0,0 +1,54 @@
+scenarios:
+ - name: "Set up trusted publishing for a new NuGet library"
+ prompt: |
+ I have a .NET class library at src/MyLib/MyLib.csproj that I want to publish
+ to nuget.org using trusted publishing (OIDC) instead of an API key.
+ The repo is hosted on GitHub at myorg/mylib. Help me set this up.
+ assertions:
+ - type: "output_contains"
+ value: "NuGet/login"
+ - type: "output_contains"
+ value: "id-token"
+ - type: "output_matches"
+ pattern: "(trusted.?publishing|OIDC)"
+ rubric:
+ - "Guides the user to create a trusted publishing policy on nuget.org"
+ - "Mentions the workflow filename must match the nuget.org policy exactly"
+ - "Includes id-token: write in the workflow permissions"
+ - "Recommends local pack verification before publishing"
+ timeout: 120
+ - name: "Set up NuGet publishing without mentioning trusted publishing"
+ prompt: |
+ I have a .NET library at src/MyLib/MyLib.csproj and I want to publish it
+ to nuget.org securely from GitHub Actions without storing API keys.
+ The repo is at myorg/mylib. How do I set this up?
+ assertions:
+ - type: "output_contains"
+ value: "NuGet/login"
+ - type: "output_contains"
+ value: "id-token"
+ - type: "output_matches"
+ pattern: "(trusted.?publishing|OIDC)"
+ rubric:
+ - "Identifies that OIDC/trusted publishing is the right approach for keyless publishing"
+ - "Creates or shows a workflow with NuGet/login and id-token: write"
+ - "Guides the user to configure a trusted publishing policy on nuget.org"
+ - "Recommends local pack verification before publishing"
+ timeout: 120
+ - name: "Migrate existing workflow from API key to trusted publishing"
+ prompt: |
+ My current publish workflow uses secrets.NUGET_API_KEY to push packages.
+ I want to switch to NuGet trusted publishing (OIDC). What do I need to change?
+ assertions:
+ - type: "output_contains"
+ value: "NuGet/login"
+ - type: "output_contains"
+ value: "id-token"
+ - type: "output_matches"
+ pattern: "(api.?key|API.?KEY|NUGET_API_KEY)"
+ rubric:
+ - "Explains how to replace the API key with NuGet/login OIDC step"
+ - "Mentions adding id-token: write permission to the job"
+ - "Advises keeping the old API key secret until trusted publishing is verified"
+ - "Mentions creating a trusted publishing policy on nuget.org"
+ timeout: 120