Summary
The dotnet plugin declares a C# (Roslyn) language server via lspServers, but it never loads in Claude Code: Total LSP servers loaded: 0. The plugin's skills load fine, which masks the problem.
Root cause: Claude Code reads a plugin's manifest — and therefore its lspServers field — only from <pluginRoot>/.claude-plugin/plugin.json. The dotnet plugin instead ships its manifest at the repo-root plugin.json (plus a .codex-plugin/plugin.json for Codex) and has no .claude-plugin/plugin.json. So Claude never sees lspServers. Skills still appear because Claude auto-discovers them from skills/*/SKILL.md frontmatter, independently of the manifest — so only the LSP (the one manifest-only feature) is silently broken.
Environment
- Claude Code
@anthropic-ai/claude-code 2.1.195 (observed on Windows 11, but this is OS-independent)
- Marketplace
dotnet-agent-skills (this repo), plugin dotnet
- .NET SDK 10.0.301;
dnx on PATH; dnx roslyn-language-server --yes --prerelease -- --stdio --autoLoadProjects launches Microsoft.CodeAnalysis.LanguageServer.exe fine when run manually
Repro
- Install the
dotnet plugin and enable it.
- Launch
claude --debug with CLAUDE_CODE_DEBUG_LOGS_DIR set, then open/edit a .cs file.
- Grep the debug log:
Total LSP servers loaded: 0
- No per-plugin LSP load attempt is logged (none of
LSP server(s) from plugin:, Failed to read/parse .lsp.json in plugin, Missing environment variables in plugin LSP config:). The loader never treats dotnet as an LSP source.
- The
dotnet skills do load.
- No
Microsoft.CodeAnalysis.LanguageServer.exe / dnx process is spawned.
Evidence that the manifest location is the cause
-
Claude Code's plugin loader requires the manifest at .claude-plugin/plugin.json (".claude-plugin/plugin.json is always required"; "No manifest found in directory. Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json").
-
Every first-party Claude Code plugin (e.g. superpowers, telegram, frontend-design) places its manifest at .claude-plugin/plugin.json and has no root plugin.json.
-
Every dotnet/skills plugin does the opposite — root plugin.json, no .claude-plugin/plugin.json:
| plugin |
.claude-plugin/plugin.json |
root plugin.json |
.codex-plugin/plugin.json |
lsp.json |
| dotnet |
❌ |
✅ |
❌ |
✅ |
| dotnet-ai / -aspnetcore / -blazor / -test / dotnet11 |
❌ |
✅ |
✅ |
❌ |
| dotnet-data / -diag / -maui / -msbuild / -nuget / -aspnet |
❌ |
✅ |
❌ |
❌ |
Likely fix (verification in progress)
Ship a .claude-plugin/plugin.json for each plugin — at minimum for dotnet, which declares the LSP — the same way .codex-plugin/plugin.json is already provided for Codex. The content can mirror the existing root plugin.json:
{
"name": "dotnet",
"version": "0.1.0",
"description": "Common everyday C#/.NET coding skills. Expected to be useful to all .NET developers.",
"skills": ["./skills/"],
"lspServers": "./lsp.json"
}
I'm validating locally by dropping exactly this .claude-plugin/plugin.json into the installed plugin and checking for Total LSP servers loaded: 1 + a spawned server process; will update this issue with the confirmed result.
Optional hardening: inline the server config under lspServers (record form) instead of the "./lsp.json" string path, e.g.
"lspServers": {
"csharp": {
"command": "dnx",
"args": ["roslyn-language-server", "--yes", "--prerelease", "--", "--stdio", "--autoLoadProjects"],
"cwd": "${PLUGIN_ROOT}",
"fileExtensions": { ".cs": "csharp", ".razor": "aspnetcorerazor", ".cshtml": "aspnetcorerazor" },
"startupTimeout": 120000
}
}
Impact
Anyone using the dotnet plugin in Claude Code for C# LSP gets no language-server intelligence (diagnostics, go-to-definition, hover, symbols) and no indication why — skills load, so the plugin looks healthy. The same root-level manifest affects all dotnet/skills plugins, but it's only observable for dotnet because that's the only one with manifest-only functionality (lspServers); the rest rely on skills, which auto-discover regardless.
Summary
The
dotnetplugin declares a C# (Roslyn) language server vialspServers, but it never loads in Claude Code:Total LSP servers loaded: 0. The plugin's skills load fine, which masks the problem.Root cause: Claude Code reads a plugin's manifest — and therefore its
lspServersfield — only from<pluginRoot>/.claude-plugin/plugin.json. Thedotnetplugin instead ships its manifest at the repo-rootplugin.json(plus a.codex-plugin/plugin.jsonfor Codex) and has no.claude-plugin/plugin.json. So Claude never seeslspServers. Skills still appear because Claude auto-discovers them fromskills/*/SKILL.mdfrontmatter, independently of the manifest — so only the LSP (the one manifest-only feature) is silently broken.Environment
@anthropic-ai/claude-code2.1.195 (observed on Windows 11, but this is OS-independent)dotnet-agent-skills(this repo), plugindotnetdnxon PATH;dnx roslyn-language-server --yes --prerelease -- --stdio --autoLoadProjectslaunchesMicrosoft.CodeAnalysis.LanguageServer.exefine when run manuallyRepro
dotnetplugin and enable it.claude --debugwithCLAUDE_CODE_DEBUG_LOGS_DIRset, then open/edit a.csfile.Total LSP servers loaded: 0LSP server(s) from plugin:,Failed to read/parse .lsp.json in plugin,Missing environment variables in plugin LSP config:). The loader never treatsdotnetas an LSP source.dotnetskills do load.Microsoft.CodeAnalysis.LanguageServer.exe/dnxprocess is spawned.Evidence that the manifest location is the cause
Claude Code's plugin loader requires the manifest at
.claude-plugin/plugin.json(".claude-plugin/plugin.jsonis always required"; "No manifest found in directory. Expected.claude-plugin/marketplace.jsonor.claude-plugin/plugin.json").Every first-party Claude Code plugin (e.g.
superpowers,telegram,frontend-design) places its manifest at.claude-plugin/plugin.jsonand has no rootplugin.json.Every
dotnet/skillsplugin does the opposite — rootplugin.json, no.claude-plugin/plugin.json:.claude-plugin/plugin.jsonplugin.json.codex-plugin/plugin.jsonlsp.jsonLikely fix (verification in progress)
Ship a
.claude-plugin/plugin.jsonfor each plugin — at minimum fordotnet, which declares the LSP — the same way.codex-plugin/plugin.jsonis already provided for Codex. The content can mirror the existing rootplugin.json:{ "name": "dotnet", "version": "0.1.0", "description": "Common everyday C#/.NET coding skills. Expected to be useful to all .NET developers.", "skills": ["./skills/"], "lspServers": "./lsp.json" }I'm validating locally by dropping exactly this
.claude-plugin/plugin.jsoninto the installed plugin and checking forTotal LSP servers loaded: 1+ a spawned server process; will update this issue with the confirmed result.Optional hardening: inline the server config under
lspServers(record form) instead of the"./lsp.json"string path, e.g.Impact
Anyone using the
dotnetplugin in Claude Code for C# LSP gets no language-server intelligence (diagnostics, go-to-definition, hover, symbols) and no indication why — skills load, so the plugin looks healthy. The same root-level manifest affects alldotnet/skillsplugins, but it's only observable fordotnetbecause that's the only one with manifest-only functionality (lspServers); the rest rely on skills, which auto-discover regardless.