Skip to content

Commit dff3eb4

Browse files
committed
fix(import): match conflict-rename map by raw or sanitized server name
After #729, ImportedServer.OriginalName carries the raw source name while the Web UI wizard keys its conflict-rename map by the sanitized preview name (Server.Name). For a server whose name needs sanitizing and is selected from 2+ sources, the httpapi rename lookup against OriginalName missed and the conflict rename was silently dropped. Match the rename map against either the raw OriginalName (CLI/documented contract) or the sanitized Server.Name (wizard), keeping both paths resolving. Add a regression test covering the conflict-rename + sanitizable-name combination on both key shapes, and update the rename field doc + regenerated OpenAPI. Related #729
1 parent 7b5bf16 commit dff3eb4

4 files changed

Lines changed: 81 additions & 12 deletions

File tree

internal/httpapi/import.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,13 @@ type ImportFromPathRequest struct {
164164
Path string `json:"path"` // File path to import from
165165
Format string `json:"format,omitempty"` // Optional format hint
166166
ServerNames []string `json:"server_names,omitempty"` // Optional: import only these servers
167-
// Rename maps OriginalName → new name. Applied after parsing so the
167+
// Rename maps a server name → new name. Applied after parsing so the
168168
// caller can disambiguate cross-source name collisions (Spec 046 v2 —
169-
// e.g. "mcpproxy" → "mcpproxy_claude_code"). Keys not present in the
170-
// imported set are ignored.
169+
// e.g. "mcpproxy" → "mcpproxy_claude_code"). Keys are matched against
170+
// either the raw source name (OriginalName) or the sanitized name shown
171+
// in the preview (Server.Name); these differ for names that need
172+
// sanitizing (e.g. "Figma Desktop" → "Figma_Desktop"). Keys not present
173+
// in the imported set are ignored.
171174
Rename map[string]string `json:"rename,omitempty"`
172175
}
173176

@@ -392,13 +395,21 @@ func (s *Server) runImport(r *http.Request, content []byte, formatHint string, s
392395
return nil, err
393396
}
394397

395-
// Spec 046 v2: apply caller-supplied renames before persisting. Keyed
396-
// by OriginalName so the caller doesn't need to know the parser's name-
397-
// sanitization rules. Server.Name is rewritten in place; OriginalName is
398-
// preserved on the response so the caller can reconcile the mapping.
398+
// Spec 046 v2: apply caller-supplied renames before persisting. The CLI and
399+
// the documented contract key the map by OriginalName (the raw source name),
400+
// while the Web UI wizard keys it by the sanitized preview name it shows the
401+
// user (Server.Name). After #729 those diverge for names that need
402+
// sanitizing (e.g. "Figma Desktop" -> "Figma_Desktop"), so match either key
403+
// to keep the conflict rename resolving on both paths (MCP-3003). Server.Name
404+
// is rewritten in place; OriginalName is preserved on the response so the
405+
// caller can reconcile the mapping.
399406
if len(rename) > 0 {
400407
for i := range result.Imported {
401-
if newName, ok := rename[result.Imported[i].OriginalName]; ok && newName != "" {
408+
newName, ok := rename[result.Imported[i].OriginalName]
409+
if !ok {
410+
newName, ok = rename[result.Imported[i].Server.Name]
411+
}
412+
if ok && newName != "" {
402413
result.Imported[i].Server.Name = newName
403414
}
404415
}

internal/httpapi/import_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,61 @@ func TestImportServersJSON_WithServerNamesFilter(t *testing.T) {
214214
}
215215
}
216216

217+
// TestRunImport_ConflictRenameSanitizableName covers the latent mismatch widened
218+
// by #729 (MCP-3003): after #729, ImportedServer.OriginalName carries the raw
219+
// source name ("Figma Desktop") while the wizard keys its conflict-rename map by
220+
// the sanitized preview name ("Figma_Desktop"). The rename map must resolve when
221+
// keyed by EITHER the raw OriginalName (CLI/documented contract) or the sanitized
222+
// Server.Name (Web UI wizard), otherwise the conflict rename is silently dropped.
223+
func TestRunImport_ConflictRenameSanitizableName(t *testing.T) {
224+
const content = `{
225+
"mcpServers": {
226+
"Figma Desktop": {"command": "figma-mcp"}
227+
},
228+
"globalShortcut": "Ctrl+M"
229+
}`
230+
231+
tests := []struct {
232+
name string
233+
renameBy string // map key the caller used
234+
}{
235+
{name: "wizard keys by sanitized Server.Name", renameBy: "Figma_Desktop"},
236+
{name: "CLI keys by raw OriginalName", renameBy: "Figma Desktop"},
237+
}
238+
239+
for _, tt := range tests {
240+
t.Run(tt.name, func(t *testing.T) {
241+
logger := zap.NewNop().Sugar()
242+
mock := &mockImportController{apiKey: "test-key"}
243+
server := NewServer(mock, logger, nil)
244+
245+
req := httptest.NewRequest("POST", "/api/v1/servers/import/path", http.NoBody)
246+
req.Header.Set("X-API-Key", "test-key")
247+
248+
const want = "Figma_Desktop_claude_desktop"
249+
rename := map[string]string{tt.renameBy: want}
250+
251+
resp, err := server.runImport(req, []byte(content), "claude-desktop", nil, true, rename)
252+
if err != nil {
253+
t.Fatalf("runImport returned error: %v", err)
254+
}
255+
if len(resp.Imported) != 1 {
256+
t.Fatalf("expected 1 imported server, got %d", len(resp.Imported))
257+
}
258+
got := resp.Imported[0]
259+
// OriginalName remains the raw source name (post-#729 contract).
260+
if got.OriginalName != "Figma Desktop" {
261+
t.Errorf("OriginalName = %q, want %q", got.OriginalName, "Figma Desktop")
262+
}
263+
// The conflict rename must have been applied regardless of which key
264+
// the caller used.
265+
if got.Name != want {
266+
t.Errorf("renamed Server.Name = %q, want %q (rename keyed by %q was dropped)", got.Name, want, tt.renameBy)
267+
}
268+
})
269+
}
270+
}
271+
217272
func TestImportServers_FileUpload(t *testing.T) {
218273
logger := zap.NewNop().Sugar()
219274
mock := &mockImportController{apiKey: "test-key"}

oas/docs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

oas/swagger.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2341,10 +2341,13 @@ components:
23412341
additionalProperties:
23422342
type: string
23432343
description: |-
2344-
Rename maps OriginalName → new name. Applied after parsing so the
2344+
Rename maps a server name → new name. Applied after parsing so the
23452345
caller can disambiguate cross-source name collisions (Spec 046 v2 —
2346-
e.g. "mcpproxy" → "mcpproxy_claude_code"). Keys not present in the
2347-
imported set are ignored.
2346+
e.g. "mcpproxy" → "mcpproxy_claude_code"). Keys are matched against
2347+
either the raw source name (OriginalName) or the sanitized name shown
2348+
in the preview (Server.Name); these differ for names that need
2349+
sanitizing (e.g. "Figma Desktop" → "Figma_Desktop"). Keys not present
2350+
in the imported set are ignored.
23482351
type: object
23492352
server_names:
23502353
description: 'Optional: import only these servers'

0 commit comments

Comments
 (0)