Mcp.ToolsDoc
0.1.1
dotnet tool install --global Mcp.ToolsDoc --version 0.1.1
dotnet new tool-manifest
dotnet tool install --local Mcp.ToolsDoc --version 0.1.1
#tool dotnet:?package=Mcp.ToolsDoc&version=0.1.1
nuke :add-package Mcp.ToolsDoc --version 0.1.1
mcp-tooling
π Language: English | Π ΡΡΡΠΊΠΈΠΉ
Shared tooling for our .NET Model Context Protocol servers.
Mcp.ToolsDoc β tool reference generator
A config-driven .NET tool that generates a Markdown tool reference for an MCP server
from its [McpServerToolType] / [McpServerTool] / [Description] attributes (Roslyn,
syntax-only β no build, runs in <1s), and a --check mode that fails CI when the committed
doc drifts from the code. Reusable across every .NET ModelContextProtocol MCP server.
Install (per consuming repo)
Add it to a local tool manifest:
dotnet new tool-manifest # if you don't have .config/dotnet-tools.json yet
dotnet tool install Mcp.ToolsDoc
Configure β toolsdoc.json at the repo root
{
// One section per MCP server. toolsDir is repo-relative.
"servers": [
{
"id": "my-mcp",
"displayName": "my-mcp",
"toolsDir": "src/MyMcp.Server/Tools",
"blurb": "Example MCP server."
}
],
"generatedOutput": "docs/TOOLS.generated.md",
// optional: keep N markers in sync
// Convention: include each plugin's SKILL.md here so its headline tool-count
// stays accurate. = sum across all servers;
// = a single server's count.
"markerFiles": [
"README.md",
"docs/INSTALL.md",
"plugins/<plugin>/skills/<skill>/SKILL.md"
],
// optional: a hand-curated cheatsheet that must mention every tool by name
"cheatsheet": "docs/TOOLS.md"
}
Only servers is required. markerFiles and cheatsheet are opt-in. The
cross-repo convention is that every plugin SKILL.md whose body mentions a
tool count is listed in markerFiles, so its headline stays accurate
automatically and the --check CI gate fails on drift. SKILL.md files
hand-maintain agent-trigger keywords and per-tool intent tables; the tool-count
headline is the one piece that can be auto-substituted, and now is.
Run
dotnet tool run mcp-toolsdoc # --write (default): (re)generate docs in place
dotnet tool run mcp-toolsdoc --check # CI: exit non-zero if anything is out of sync
Options: --config <path> (default <repo-root>/toolsdoc.json), --repo-root <path>
(default: the git root found from the current directory).
CI integration
# .github/workflows/docs-codegen.yml
on: { push: { branches: [main] }, pull_request: { branches: [main] } }
jobs:
toolsdoc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '10.0.x' }
- run: dotnet tool restore
- run: dotnet tool run mcp-toolsdoc --check
The generated TOOLS.generated.md is English-only; if your repo enforces a bilingual-docs
gate, list it in that gate's ignore file.
Mcp.I18nCheck β bilingual-docs gate
A config-less .NET tool that enforces our bilingual-docs convention in CI: English is canonical; every English doc must have its Russian counterpart and the Russian file must be non-stub (β₯ 200 bytes).
docs/<path>.mdβdocs/ru/<path>.md(mirror subtree).X.mdβX.ru.md(suffix) for the repo root,plugins/*,examples/**,servers/*,infra/.
Exemptions: a repo-root .i18nignore (English-only / generated / agent files, one
repo-relative path per line). Pairs outside the conventional locations: .i18npairs
(en:ru per line).
dotnet tool install Mcp.I18nCheck
dotnet tool run mcp-i18ncheck # exit non-zero if any pair is missing/stub
# .github/workflows/docs-i18n.yml
on: { push: { branches: [main] }, pull_request: { branches: [main] } }
jobs:
bilingual-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '10.0.x' }
- run: dotnet tool restore
- run: dotnet tool run mcp-i18ncheck
Reusable CI/CD workflows
Two reusable GitHub Actions workflows
let every consuming repo share one definition of how images are built, published, and deployed β
callers stay thin and never drift apart. They live under .github/workflows/ here and are
referenced with uses: <owner>/mcp-tooling/.github/workflows/<file>@main.
docker-build-push.yml β build + push ghcr.io images
Builds one or more multi-arch images from a JSON image matrix and pushes each as
ghcr.io/<owner-lowercase>/<image_suffix> with :<version> and :latest.
# .github/workflows/docker.yml in the consuming repo
on:
push: { tags: ['v*.*.*'] }
workflow_dispatch:
inputs:
version: { description: 'Version without leading v', required: true, type: string }
jobs:
build-push:
permissions: { contents: read, packages: write }
uses: <owner>/mcp-tooling/.github/workflows/docker-build-push.yml@main
with:
version: ${{ inputs.version != '' && inputs.version || github.ref_name }}
images: |
[
{ "name": "my-mcp", "image_suffix": "my-mcp",
"dockerfile": "./Dockerfile", "context": ".", "cache_scope": "my-mcp" }
]
# use_gh_packages_secret: true # when the Dockerfile restores a private GH Packages feed
# secrets:
# gh_packages_token: ${{ secrets.GITHUB_TOKEN }}
| input | meaning |
|---|---|
version |
image tag; a leading v is stripped; :latest is also pushed |
images |
JSON array of {name, image_suffix, dockerfile, context, cache_scope} (one entry per image) |
platforms |
buildx platforms (default linux/amd64,linux/arm64) |
use_gh_packages_secret |
mount github_token as a build-secret for private NuGet restore |
deploy-vps.yml β ship an image to a VPS over SSH
Ships a published image to a host without any registry login on the host: the runner pulls
the image, docker save | ssh streams the tarball into a forced-command deploy.sh, the tag
arrives as the SSH command (re-validated server-side), then the runner smoke-tests a healthz URL.
# .github/workflows/deploy.yml in the consuming repo
on:
workflow_dispatch:
inputs:
tag: { description: 'Image tag (semver or latest)', required: true, type: string, default: latest }
jobs:
deploy:
uses: <owner>/mcp-tooling/.github/workflows/deploy-vps.yml@main
with:
image_suffix: my-mcp
tag: ${{ inputs.tag }}
healthz_url: https://my-mcp.example.com/healthz
secrets:
deploy_ssh_key: ${{ secrets.DEPLOY_SSH_KEY }}
deploy_host: ${{ secrets.DEPLOY_HOST }}
deploy_user: ${{ secrets.DEPLOY_USER }}
deploy_known_hosts: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
Host prerequisite (per service): a CI deploy key locked to the forced command in the host's
~/.ssh/authorized_keys, so the key can only run the deploy script and nothing else:
command="/opt/<name>/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA... ci-deploy
deploy.sh validates the tag, docker loads the piped tarball, pins it in the compose .env,
recreates the container, and waits for the healthcheck. A reference script is kept in each
consuming repo under deploy/deploy.sh.
Releasing
Each tool's version lives in its csproj (src/Mcp.ToolsDoc, src/Mcp.I18nCheck). Pushing a
v X.Y.Z tag packs both and publishes them to nuget.org via
.github/workflows/publish.yml (--skip-duplicate, so unchanged versions are no-ops; requires
the repo secret NUGET_API_KEY). Bump the relevant csproj <Version> before tagging.
License: MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.