Skip to content

Commit aaa634e

Browse files
rdimitrovclaude
andauthored
fix(publisher): honor mcpName as the authoritative server name in init (#1195)
## Summary Follow-up to #1145. When `mcpName` is set in `package.json`, it should be the server name used by `mcp-publisher init`. Two issues addressed: ### 1. `getNameFromPackageJSON` mangled `mcpName` through npm-name transformation `mcpName` values are already in MCP server name format (e.g. `io.github.foo/bar`). The existing code ran them through the `@scope/name → io.github.scope/name` transform and its `io.github.<your-username>/*` fallback, producing a broken double-prefix result. Example with `"mcpName": "io.github.acme/weather"`: - Before: `io.github.<your-username>/io.github.acme/weather` ❌ - After: `io.github.acme/weather` ✅ ### 2. `detectServerName` consulted git before `mcpName` Even with `mcpName` set in `package.json`, a github.com git remote (or a `repository` field) silently won — the function returned `io.github.<owner>/<repo>` and never looked at `mcpName`. Per the [quickstart docs](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx), *"The value of `mcpName` will be your server's name in the MCP Registry"*, so `mcpName` should take precedence. ## Changes - `cmd/publisher/commands/init.go`: - New `getMcpNameFromPackageJSON()` helper that returns `mcpName` or `""`. - `detectServerName` checks `mcpName` first, before git remote and the `name`-based fallback. - `getNameFromPackageJSON` simplified — the now-redundant `mcpName` branch was removed; it only handles the npm `name` transformation. - `cmd/publisher/commands/init_test.go` (new): 5 cases through the public `InitCommand` API covering `mcpName` as-is, fallback to scoped `name`, fallback to placeholder, version fallback, and `mcpName` winning over a GitHub `repository` URL. This completes the fix for #737. ## Test plan - [x] `go test ./cmd/publisher/commands/` passes (5 new subtests) - [x] `golangci-lint run ./cmd/publisher/commands/` clean - [ ] Manual: `mcp-publisher init` in a git-tracked directory with `package.json` containing `mcpName` produces a `server.json` whose `name` matches `mcpName`, not the repo slug 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 7c446e7 commit aaa634e

2 files changed

Lines changed: 122 additions & 7 deletions

File tree

‎cmd/publisher/commands/init.go‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,26 @@ func detectSubfolder() string {
132132
return filepath.ToSlash(relPath)
133133
}
134134

135+
// getMcpNameFromPackageJSON returns the `mcpName` field from package.json, or
136+
// "" if not set. mcpName is the authoritative MCP server name when present —
137+
// see docs/modelcontextprotocol-io/quickstart.mdx.
138+
func getMcpNameFromPackageJSON() string {
139+
data, err := os.ReadFile("package.json")
140+
if err != nil {
141+
return ""
142+
}
143+
144+
var pkg map[string]any
145+
if err := json.Unmarshal(data, &pkg); err != nil {
146+
return ""
147+
}
148+
149+
if mcpName, ok := pkg["mcpName"].(string); ok && mcpName != "" {
150+
return mcpName
151+
}
152+
return ""
153+
}
154+
135155
func getNameFromPackageJSON() string {
136156
data, err := os.ReadFile("package.json")
137157
if err != nil {
@@ -143,17 +163,13 @@ func getNameFromPackageJSON() string {
143163
return ""
144164
}
145165

146-
// Prefer mcpName over name, as the server name must match mcpName
147-
name, ok := pkg["mcpName"].(string)
166+
name, ok := pkg["name"].(string)
148167
if !ok || name == "" {
149-
name, ok = pkg["name"].(string)
150-
if !ok || name == "" {
151-
return ""
152-
}
168+
return ""
153169
}
154170

155171
// Convert npm package name to MCP server name
156-
// @org/package -> io.npm.org/package
172+
// @org/package -> io.github.org/package
157173
if strings.HasPrefix(name, "@") {
158174
parts := strings.Split(name[1:], "/")
159175
if len(parts) == 2 {
@@ -183,6 +199,13 @@ func getVersionFromPackageJSON() string {
183199
}
184200

185201
func detectServerName(subfolder string) string {
202+
// mcpName in package.json is the authoritative server name when set, so it
203+
// takes precedence over names inferred from the git remote or the npm
204+
// `name` field.
205+
if name := getMcpNameFromPackageJSON(); name != "" {
206+
return name
207+
}
208+
186209
// Try to get from git remote
187210
repoURL := detectRepoURL()
188211
if repoURL != "" && strings.Contains(repoURL, "github.com") {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package commands_test
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/modelcontextprotocol/registry/cmd/publisher/commands"
10+
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestInitCommand_PackageJSON(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
pkgJSON string
19+
expectedName string
20+
expectedVersion string
21+
}{
22+
{
23+
name: "mcpName is returned as-is without transformation",
24+
pkgJSON: `{"name": "@acme/weather", "mcpName": "io.github.acme/weather-mcp", "version": "2.3.4", "repository": "https://gitlab.com/acme/weather"}`,
25+
expectedName: "io.github.acme/weather-mcp",
26+
expectedVersion: "2.3.4",
27+
},
28+
{
29+
name: "empty mcpName falls back to scoped name transformation",
30+
pkgJSON: `{"name": "@acme/weather", "mcpName": "", "version": "1.2.3", "repository": "https://gitlab.com/acme/weather"}`,
31+
expectedName: "io.github.acme/weather",
32+
expectedVersion: "1.2.3",
33+
},
34+
{
35+
name: "missing mcpName falls back to scoped name transformation",
36+
pkgJSON: `{"name": "@acme/weather", "version": "1.2.3", "repository": "https://gitlab.com/acme/weather"}`,
37+
expectedName: "io.github.acme/weather",
38+
expectedVersion: "1.2.3",
39+
},
40+
{
41+
name: "missing version falls back to 1.0.0",
42+
pkgJSON: `{"name": "@acme/weather", "mcpName": "io.github.acme/weather", "repository": "https://gitlab.com/acme/weather"}`,
43+
expectedName: "io.github.acme/weather",
44+
expectedVersion: "1.0.0",
45+
},
46+
{
47+
name: "mcpName takes precedence over GitHub repository URL",
48+
pkgJSON: `{"name": "weather", "mcpName": "io.github.acme/weather-mcp", "version": "1.0.0", "repository": "https://github.com/someone-else/some-repo"}`,
49+
expectedName: "io.github.acme/weather-mcp",
50+
expectedVersion: "1.0.0",
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
dir := withIsolatedPackageJSON(t, tt.pkgJSON)
57+
58+
require.NoError(t, commands.InitCommand())
59+
60+
data, err := os.ReadFile(filepath.Join(dir, "server.json"))
61+
require.NoError(t, err)
62+
63+
var got apiv0.ServerJSON
64+
require.NoError(t, json.Unmarshal(data, &got))
65+
66+
assert.Equal(t, tt.expectedName, got.Name)
67+
assert.Equal(t, tt.expectedVersion, got.Version)
68+
require.Len(t, got.Packages, 1)
69+
assert.Equal(t, tt.expectedVersion, got.Packages[0].Version,
70+
"package version should match server version")
71+
})
72+
}
73+
}
74+
75+
// withIsolatedPackageJSON creates a temp dir outside any git repository,
76+
// writes package.json with the given contents, chdirs into it, and returns
77+
// the directory path. The HOME override prevents init from detecting the
78+
// host repo's git state.
79+
func withIsolatedPackageJSON(t *testing.T, contents string) string {
80+
t.Helper()
81+
82+
dir := t.TempDir()
83+
require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(contents), 0600))
84+
85+
originalDir, err := os.Getwd()
86+
require.NoError(t, err)
87+
t.Cleanup(func() { _ = os.Chdir(originalDir) })
88+
89+
require.NoError(t, os.Chdir(dir))
90+
91+
return dir
92+
}

0 commit comments

Comments
 (0)