Skip to content

Commit 224be6c

Browse files
authored
Add optional flag to mcpd add to --allow-deprecatd (#134)
* Allows server installations marked as deprecated to be added to mcpd config, without it they are rejected.
1 parent 8ce7bda commit 224be6c

File tree

3 files changed

+93
-23
lines changed

3 files changed

+93
-23
lines changed

cmd/add.go

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type AddCmd struct {
2828
Runtime string
2929
Source string
3030
Format internalcmd.OutputFormat
31+
AllowDeprecated bool
3132
cfgLoader config.Loader
3233
packagePrinter output.Printer[config.ServerEntry]
3334
registryBuilder registry.Builder
@@ -92,9 +93,24 @@ func NewAddCmd(baseCmd *internalcmd.BaseCmd, opt ...cmdopts.CmdOption) (*cobra.C
9293
fmt.Sprintf("Specify the output format (one of: %s)", allowed.String()),
9394
)
9495

96+
cobraCommand.Flags().BoolVar(
97+
&c.AllowDeprecated,
98+
"allow-deprecated",
99+
false,
100+
"Optional, allows server installations marked as deprecated to be added",
101+
)
102+
95103
return cobraCommand, nil
96104
}
97105

106+
// serverEntryOptions contains configuration for parsing a server entry
107+
type serverEntryOptions struct {
108+
Runtime runtime.Runtime
109+
Tools []string
110+
SupportedRuntimes []runtime.Runtime
111+
AllowDeprecated bool
112+
}
113+
98114
// run is configured (via NewAddCmd) to be called by the Cobra framework when the command is executed.
99115
// It may return an error (or nil, when there is no error).
100116
func (c *AddCmd) run(cmd *cobra.Command, args []string) error {
@@ -138,9 +154,15 @@ func (c *AddCmd) run(cmd *cobra.Command, args []string) error {
138154
)
139155
}
140156

141-
entry, err := parseServerEntry(pkg, runtime.Runtime(c.Runtime), c.Tools, c.MCPDSupportedRuntimes())
157+
opts := serverEntryOptions{
158+
Runtime: runtime.Runtime(c.Runtime),
159+
Tools: c.Tools,
160+
SupportedRuntimes: c.MCPDSupportedRuntimes(),
161+
AllowDeprecated: c.AllowDeprecated,
162+
}
163+
entry, err := parseServerEntry(pkg, opts)
142164
if err != nil {
143-
return handler.HandleError(fmt.Errorf("error parsing server entry: %w", err))
165+
return handler.HandleError(err)
144166
}
145167

146168
cfg, err := c.cfgLoader.Load(flags.ConfigFile)
@@ -208,39 +230,49 @@ func selectRuntime(
208230
return "", fmt.Errorf("no supported runtimes found")
209231
}
210232

211-
func parseServerEntry(
212-
pkg packages.Server,
213-
requestedRuntime runtime.Runtime,
214-
requestedTools []string,
215-
supportedRuntimes []runtime.Runtime,
216-
) (config.ServerEntry, error) {
217-
requestedTools, err := filter.MatchRequestedSlice(requestedTools, pkg.Tools.Names())
233+
func parseServerEntry(pkg packages.Server, opts serverEntryOptions) (config.ServerEntry, error) {
234+
requestedTools, err := filter.MatchRequestedSlice(opts.Tools, pkg.Tools.Names())
218235
if err != nil {
219236
return config.ServerEntry{}, fmt.Errorf("error matching requested tools: %w", err)
220237
}
221238

222-
selectedRuntime, runtimeErr := selectRuntime(pkg.Installations, requestedRuntime, supportedRuntimes)
223-
if runtimeErr != nil {
224-
return config.ServerEntry{}, fmt.Errorf("error selecting runtime from available installations: %w", runtimeErr)
239+
selectedRuntime, err := selectRuntime(pkg.Installations, opts.Runtime, opts.SupportedRuntimes)
240+
if err != nil {
241+
return config.ServerEntry{}, fmt.Errorf("error selecting runtime from available installations: %w", err)
242+
}
243+
244+
installation, ok := pkg.Installations[selectedRuntime]
245+
if !ok {
246+
return config.ServerEntry{}, fmt.Errorf(
247+
"installation not found for runtime '%s'",
248+
selectedRuntime,
249+
)
225250
}
226251

227-
v := "latest"
228-
if installation, ok := pkg.Installations[selectedRuntime]; ok && installation.Version != "" {
229-
v = installation.Version
252+
if installation.Deprecated && !opts.AllowDeprecated {
253+
return config.ServerEntry{}, fmt.Errorf(
254+
"server '%s' with runtime '%s' is deprecated, use --allow-deprecated flag to proceed",
255+
pkg.ID,
256+
selectedRuntime,
257+
)
230258
}
231259

232-
runtimeSpecificName := pkg.Installations[selectedRuntime].Package
233-
if runtimeSpecificName == "" {
260+
if installation.Package == "" {
234261
return config.ServerEntry{}, fmt.Errorf(
235262
"installation package name is missing for runtime '%s'",
236263
selectedRuntime,
237264
)
238265
}
239-
runtimePackageVersion := fmt.Sprintf("%s::%s@%s", selectedRuntime, runtimeSpecificName, v)
240266

241-
envs := packages.FilterArguments(pkg.Arguments, packages.EnvVar, packages.Required)
242-
args := packages.FilterArguments(pkg.Arguments, packages.ValueArgument, packages.Required)
243-
boolArgs := packages.FilterArguments(pkg.Arguments, packages.BoolArgument, packages.Required)
267+
version := "latest"
268+
if installation.Version != "" {
269+
version = installation.Version
270+
}
271+
272+
runtimePackageVersion := fmt.Sprintf("%s::%s@%s", selectedRuntime, installation.Package, version)
273+
envs := pkg.Arguments.FilterBy(packages.Required, packages.EnvVar)
274+
args := pkg.Arguments.FilterBy(packages.Required, packages.ValueAcceptingArgument)
275+
boolArgs := pkg.Arguments.FilterBy(packages.Required, packages.BoolArgument)
244276

245277
return config.ServerEntry{
246278
Name: pkg.ID,

cmd/add_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ func TestSelectRuntime(t *testing.T) {
411411
}
412412
}
413413

414+
func intPtr(i int) *int {
415+
return &i
416+
}
417+
414418
func TestParseServerEntry(t *testing.T) {
415419
t.Parallel()
416420

@@ -515,6 +519,28 @@ func TestParseServerEntry(t *testing.T) {
515519
expectedPackageValue: "uvx::mcp-server-api@latest",
516520
expectedRequiredValues: []string{"--endpoint", "--api-key"},
517521
},
522+
{
523+
name: "server with positional and value args",
524+
installations: map[runtime.Runtime]packages.Installation{
525+
runtime.UVX: {
526+
Package: "mcp-server-files",
527+
Recommended: true,
528+
},
529+
},
530+
supportedRuntimes: []runtime.Runtime{runtime.UVX},
531+
pkgName: "files",
532+
pkgID: "files",
533+
availableTools: []string{"read", "write"},
534+
requestedTools: []string{"read"},
535+
arguments: packages.Arguments{
536+
"path": {VariableType: packages.VariableTypeArgPositional, Position: intPtr(1), Required: true},
537+
"mode": {VariableType: packages.VariableTypeArgPositional, Position: intPtr(2), Required: true},
538+
"--format": {VariableType: packages.VariableTypeArg, Required: true},
539+
"--encoding": {VariableType: packages.VariableTypeArg, Required: false},
540+
},
541+
expectedPackageValue: "uvx::mcp-server-files@latest",
542+
expectedRequiredValues: []string{"path", "mode", "--format"},
543+
},
518544
{
519545
name: "server with only required bool args",
520546
installations: map[runtime.Runtime]packages.Installation{
@@ -626,7 +652,13 @@ func TestParseServerEntry(t *testing.T) {
626652
Arguments: tc.arguments,
627653
}
628654

629-
entry, err := parseServerEntry(pkg, tc.requestedRuntime, tc.requestedTools, tc.supportedRuntimes)
655+
opts := serverEntryOptions{
656+
Runtime: tc.requestedRuntime,
657+
Tools: tc.requestedTools,
658+
SupportedRuntimes: tc.supportedRuntimes,
659+
AllowDeprecated: false,
660+
}
661+
entry, err := parseServerEntry(pkg, opts)
630662

631663
if tc.isErrorExpected {
632664
require.Error(t, err)

internal/packages/argument.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,11 @@ func PositionalArgument(_ string, data ArgumentMetadata) bool {
146146

147147
// NonPositionalArgument is a predicate that requires the argument is not a positional command line argument.
148148
func NonPositionalArgument(s string, data ArgumentMetadata) bool {
149-
return !PositionalArgument(s, data)
149+
return !PositionalArgument(s, data) && !EnvVar(s, data)
150+
}
151+
152+
// ValueAcceptingArgument is a predicate that requires an argument is capable of accepting a value.
153+
// This means it must be an argument (as opposed to env var) and cannot be a boolean flag.
154+
func ValueAcceptingArgument(_ string, data ArgumentMetadata) bool {
155+
return data.VariableType == VariableTypeArgPositional || data.VariableType == VariableTypeArg
150156
}

0 commit comments

Comments
 (0)