9 files changed
+248
-1
.agents/AGENTS.md
+2
@@ -101,6 +101,8 @@ make patch | make minor | make major | make custom VERSION=0.x.y
101
102
It commits `Release <version>` and tags `v<version>` locally; pushing is manual. Crate publishing is separate and manual (`cargo workspaces publish`, see `docs/development.md`). The per-target release workflows (`release-*.yml`) build the distributable artifacts.
103
104
+`server.json` (the MCP Registry listing) is version-synced by the same Makefile step. Run `release-mcp-registry.yml` last, after the npm and NuGet packages for that version are live — the registry validates its metadata against the real packages. See `docs/mcp-registry.md`.
105
+
106
---
107
108
# Architecture
.github/workflows/release-mcp-registry.yml
new
+87
@@ -0,0 +1,87 @@
1
+name: MCP Registry Release
2
+
3
+on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ tag:
7
+ description: "Release tag (e.g. v0.4.12)"
8
+ required: true
9
+
10
+# OIDC is how we authenticate to the MCP Registry: the registry trusts a GitHub
11
+# Actions token issued for this repository, so no long-lived secret is needed.
12
+permissions:
13
+ id-token: write
14
+ contents: read
15
+
16
+jobs:
17
+ publish:
18
+ name: Publish server.json to the MCP Registry
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v6
23
+ with:
24
+ ref: ${{ github.event.inputs.tag || github.ref }}
25
+
26
+ - name: Set the release version
27
+ shell: bash
28
+ run: |
29
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
30
+ release_version="${GITHUB_REF_NAME#v}"
31
+ else
32
+ release_version="${{ github.event.inputs.tag }}"
33
+ release_version="${release_version#v}"
34
+ fi
35
+
36
+ echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
37
+
38
+ - name: Check the server name is consistent across packages
39
+ shell: bash
40
+ run: |
41
+ server_name="$(jq -r '.name' server.json)"
42
+ echo "Server name: ${server_name}"
43
+
44
+ # npm proves ownership through `mcpName` in the published package.json,
45
+ # which is generated by this script at release time.
46
+ grep -q "mcpName: \"${server_name}\"" npm/scripts/render-main-package.cjs
47
+
48
+ # NuGet proves ownership through an `mcp-name:` marker in the package README.
49
+ grep -q "mcp-name: ${server_name}" nuget/smbcloud-cli/README.md
50
+
51
+ - name: Set the version in server.json
52
+ shell: bash
53
+ run: |
54
+ jq --arg v "${RELEASE_VERSION}" \
55
+ '.version = $v | .packages |= map(.version = $v)' \
56
+ server.json > server.tmp
57
+ mv server.tmp server.json
58
+ cat server.json
59
+
60
+ - name: Verify the packages are published
61
+ shell: bash
62
+ run: |
63
+ # The registry only stores metadata and validates it against the real
64
+ # packages, so both must already exist at this version.
65
+ npm view "@smbcloud/cli@${RELEASE_VERSION}" version
66
+
67
+ nuget_versions="$(curl -fsSL https://api.nuget.org/v3-flatcontainer/smbcloud.cli/index.json)"
68
+ echo "${nuget_versions}" | jq -e --arg v "${RELEASE_VERSION}" '.versions | index($v)'
69
+
70
+ - name: Install mcp-publisher
71
+ shell: bash
72
+ run: |
73
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
74
+
75
+ - name: Authenticate to the MCP Registry
76
+ shell: bash
77
+ run: ./mcp-publisher login github-oidc
78
+
79
+ - name: Publish
80
+ shell: bash
81
+ run: ./mcp-publisher publish
82
+
83
+ - name: Verify the server is listed
84
+ shell: bash
85
+ run: |
86
+ server_name="$(jq -r '.name' server.json)"
87
+ curl -fsSL "https://registry.modelcontextprotocol.io/v0.1/servers?search=${server_name}" | jq .
docs/mcp-registry.md
new
+84
@@ -0,0 +1,84 @@
1
+# MCP Registry
2
+
3
+The smbCloud CLI's MCP server is listed in the [official MCP Registry](https://registry.modelcontextprotocol.io)
4
+as **`io.github.smbcloudxyz/smbcloud-cli`**. The registry is a metadata index —
5
+it doesn't host binaries — so the listing points at packages we already publish
6
+to npm and NuGet, and clients that browse the registry can offer a one-click
7
+install of `smb --mcp`.
8
+
9
+For running and configuring the server itself, see [MCP Server](./mcp.md).
10
+
11
+## What makes up the listing
12
+
13
+| Piece | Where |
14
+| --- | --- |
15
+| Server metadata | [`server.json`](../server.json) at the repo root |
16
+| npm ownership proof | `mcpName` in the generated `@smbcloud/cli` `package.json` (`npm/scripts/render-main-package.cjs`) |
17
+| NuGet ownership proof | `<!-- mcp-name: ... -->` in `nuget/smbcloud-cli/README.md` |
18
+| Publishing | `.github/workflows/release-mcp-registry.yml` |
19
+
20
+The registry checks each listed package for a marker naming the server. If the
21
+marker is missing or doesn't match `name` in `server.json`, publishing fails
22
+with "Registry validation failed for package". The publish workflow greps for
23
+both markers before it does anything else, so a mismatch shows up as a failed
24
+job rather than a rejected upload.
25
+
26
+### Why the name is `io.github.smbcloudxyz/...`
27
+
28
+The namespace has to match the authentication method. We authenticate with
29
+GitHub OIDC from Actions, which grants the `io.github.smbcloudxyz/*` namespace
30
+(the reverse-DNS form of the `smbcloudXYZ` org). Publishing under
31
+`xyz.smbcloud/*` instead would mean domain-based auth: a `v=MCPv1` TXT record on
32
+`smbcloud.xyz` plus an Ed25519 private key stored as a repo secret. OIDC needs
33
+no secret at all, so that's what we use.
34
+
35
+### Which packages are listed
36
+
37
+- **npm** — `@smbcloud/cli`, launched as `npx @smbcloud/cli --mcp`. The package
38
+ declares a single binary (`smb`), so `npx` resolves it despite the name
39
+ difference.
40
+- **NuGet** — `SmbCloud.Cli`, launched as `dnx SmbCloud.Cli --mcp`.
41
+
42
+PyPI is deliberately not listed. The `smbcloud-cli` wheel installs its
43
+executable as `smb` (maturin `bindings = "bin"`), and `uvx smbcloud-cli` looks
44
+for an executable matching the distribution name, so the launch command the
45
+registry hands to clients wouldn't work. Adding PyPI would mean shipping an
46
+extra console script named `smbcloud-cli`. Homebrew and the direct GitHub
47
+release binaries have no registry package type at all; those install paths stay
48
+documented in [Install](./cli-install.md).
49
+
50
+## Releasing a new version
51
+
52
+`server.json` carries the version twice — once for the server, once per package
53
+— and both are updated by `make patch | minor | major` along with the rest of
54
+the release metadata (`scripts/sync-release-version.mjs`).
55
+
56
+After tagging and running the npm and NuGet release workflows, run the
57
+**MCP Registry Release** workflow with the same tag:
58
+
59
+```sh
60
+gh workflow run release-mcp-registry.yml -f tag=v0.4.12
61
+```
62
+
63
+Order matters. The registry fetches the package metadata during publish, so
64
+`@smbcloud/cli@<version>` and `SmbCloud.Cli <version>` must already be live —
65
+the workflow checks for both and stops early if they aren't.
66
+
67
+Confirm the listing afterwards:
68
+
69
+```sh
70
+curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.smbcloudxyz/smbcloud-cli"
71
+```
72
+
73
+## Publishing by hand
74
+
75
+Rarely needed, but if Actions is unavailable:
76
+
77
+```sh
78
+brew install mcp-publisher
79
+mcp-publisher login github # device flow, needs push access to smbcloudXYZ
80
+mcp-publisher publish # reads ./server.json
81
+```
82
+
83
+Versions are immutable — republishing the same version is rejected. Fixing a
84
+bad listing means shipping a new patch version.
docs/mcp.md
+3
-1
@@ -9,7 +9,9 @@ tools covering the account, project, tenant, Mail, and Auth surfaces.
9
10
This page is the setup guide and the full tool reference. For how `--mcp`
11
compares to the CLI's other two interfaces (headless and `--tui`), see
12
-[Interfaces](./interfaces.md).
12
+[Interfaces](./interfaces.md). The server is also listed in the official MCP
13
+Registry as `io.github.smbcloudxyz/smbcloud-cli`, so clients that browse the
14
+registry can install it for you — see [MCP Registry](./mcp-registry.md).
15
16
## Prerequisites
17
npm/package-main.json.tmpl
+1
@@ -1,6 +1,7 @@
1
{
2
"name": "@smbcloud/cli",
3
"version": "${release_version}",
4
+ "mcpName": "io.github.smbcloudxyz/smbcloud-cli",
5
"keywords": [
6
"smbcloud",
7
"cli",
npm/scripts/render-main-package.cjs
+2
@@ -12,6 +12,8 @@ if (!outputPath || !version) {
12
const packageJson = {
13
name: "@smbcloud/cli",
14
version,
15
+ // Ownership proof for the MCP Registry: must match `name` in ../../server.json.
16
+ mcpName: "io.github.smbcloudxyz/smbcloud-cli",
17
keywords: [
18
"smbcloud",
19
"cli",
nuget/smbcloud-cli/README.md
+2
@@ -1,5 +1,7 @@
1
# smbCloud CLI for .NET
2
3
+<!-- mcp-name: io.github.smbcloudxyz/smbcloud-cli -->
4
+
5
`smb` is the command-line interface for [smbCloud](https://smbcloud.xyz/).
6
7
## Install
scripts/sync-release-version.mjs
+16
@@ -8,6 +8,7 @@ const authGemVersionPath = resolve(repoRoot, "sdk/gems/auth/lib/auth/version.rb"
8
const authGemCargoTomlPath = resolve(repoRoot, "sdk/gems/auth/ext/auth/Cargo.toml");
9
const modelGemVersionPath = resolve(repoRoot, "sdk/gems/model/lib/model/version.rb");
10
const modelGemCargoTomlPath = resolve(repoRoot, "sdk/gems/model/ext/model/Cargo.toml");
11
+const mcpServerJsonPath = resolve(repoRoot, "server.json");
12
13
function readText(path) {
14
return readFileSync(path, "utf8");
@@ -129,6 +130,21 @@ if (
130
updatedPaths.push(modelGemCargoTomlPath);
131
}
132
133
+if (
134
+ updateFile(mcpServerJsonPath, (content) => {
135
+ const parsed = JSON.parse(content);
136
+ parsed.version = releaseVersion;
137
+
138
+ for (const pkg of parsed.packages ?? []) {
139
+ pkg.version = releaseVersion;
140
+ }
141
+
142
+ return `${JSON.stringify(parsed, null, 2)}\n`;
143
+ })
144
+) {
145
+ updatedPaths.push(mcpServerJsonPath);
146
+}
147
+
148
console.log(`Release version: ${releaseVersion}`);
149
150
if (updatedPaths.length === 0) {
server.json
new
+51
@@ -0,0 +1,51 @@
1
+{
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.smbcloudxyz/smbcloud-cli",
4
+ "title": "smbCloud CLI",
5
+ "description": "Manage smbCloud projects, deployments, tenants, Mail apps, and Auth apps.",
6
+ "version": "0.4.12",
7
+ "websiteUrl": "https://smbcloud.xyz/",
8
+ "repository": {
9
+ "url": "https://github.com/smbcloudXYZ/smbcloud-cli",
10
+ "source": "github",
11
+ "id": "597171611"
12
+ },
13
+ "packages": [
14
+ {
15
+ "registryType": "npm",
16
+ "registryBaseUrl": "https://registry.npmjs.org",
17
+ "identifier": "@smbcloud/cli",
18
+ "version": "0.4.12",
19
+ "runtimeHint": "npx",
20
+ "transport": {
21
+ "type": "stdio"
22
+ },
23
+ "packageArguments": [
24
+ {
25
+ "type": "named",
26
+ "name": "--mcp",
27
+ "description": "Run the CLI as an MCP server over stdio instead of executing a one-shot command.",
28
+ "isRequired": true
29
+ }
30
+ ]
31
+ },
32
+ {
33
+ "registryType": "nuget",
34
+ "registryBaseUrl": "https://api.nuget.org/v3/index.json",
35
+ "identifier": "SmbCloud.Cli",
36
+ "version": "0.4.12",
37
+ "runtimeHint": "dnx",
38
+ "transport": {
39
+ "type": "stdio"
40
+ },
41
+ "packageArguments": [
42
+ {
43
+ "type": "named",
44
+ "name": "--mcp",
45
+ "description": "Run the CLI as an MCP server over stdio instead of executing a one-shot command.",
46
+ "isRequired": true
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+}