Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/internal/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func (opts *ToolboxOptions) Setup(ctx context.Context) (context.Context, func(co
ctx = util.WithLogger(ctx, logger)
opts.Logger = logger

ctx = util.WithIgnoreUnknownTools(ctx, opts.Cfg.IgnoreUnknownTools)

logger.InfoContext(ctx, fmt.Sprintf("Starting MCP Toolbox for Databases version %s", opts.Cfg.Version))

// Set up OpenTelemetry
Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func NewCommand(opts *internal.ToolboxOptions) *cobra.Command {
internal.ConfigFileFlags(flags, opts)
internal.ServeFlags(flags, opts)
flags.BoolVar(&opts.Cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.")
flags.BoolVar(&opts.Cfg.IgnoreUnknownTools, "ignore-unknown-tools", false, "Log warnings and skip unknown/unsupported tool types instead of failing to start.")
flags.IntVar(&opts.Cfg.PollInterval, "poll-interval", 0, "Specifies the polling frequency (seconds) for configuration file updates.")
// wrap RunE command so that we have access to original Command object
cmd.RunE = func(*cobra.Command, []string) error { return run(cmd, opts) }
Expand Down Expand Up @@ -178,6 +179,7 @@ func validateReloadEdits(
ToolConfigs: toolsFile.Tools,
ToolsetConfigs: toolsFile.Toolsets,
PromptConfigs: toolsFile.Prompts,
IgnoreUnknownTools: util.IgnoreUnknownToolsFromContext(ctx),
}

sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, err := server.InitializeConfigs(ctx, reloadedConfig)
Expand Down
46 changes: 46 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -954,3 +954,49 @@ func TestSubcommandWiring(t *testing.T) {
}
}
}

func TestIgnoreUnknownToolsFlag(t *testing.T) {
invalidContent := `
kind: tool
name: invalid_tool
type: unregistered-tool-type
source: my-http
description: "A tool with unregistered type"
---
kind: source
name: my-http
type: http
baseUrl: http://example.com
`
invalidFile := filepath.Join(t.TempDir(), "invalid.yaml")
if err := os.WriteFile(invalidFile, []byte(invalidContent), 0644); err != nil {
t.Fatal(err)
}

t.Run("without ignore flag fails", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, _, _, err := invokeCommandWithContext(ctx, []string{"--config", invalidFile})
if err == nil {
t.Fatalf("expected error due to unregistered tool type, got nil")
}
if !strings.Contains(err.Error(), "unknown tool type") {
t.Errorf("expected error containing 'unknown tool type', got: %v", err)
}
})

t.Run("with ignore flag succeeds and skips tool", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
_, opts, output, err := invokeCommandWithContext(ctx, []string{"--config", invalidFile, "--ignore-unknown-tools"})
if err != nil && err != context.DeadlineExceeded && err != context.Canceled {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(output, "Server ready to serve!") {
t.Errorf("server did not start successfully. Output:\n%s", output)
}
if _, ok := opts.Cfg.ToolConfigs["invalid_tool"]; ok {
t.Errorf("expected 'invalid_tool' to be skipped and filtered out, but it was found in ToolConfigs")
}
})
Comment thread
Yuan325 marked this conversation as resolved.
}
1 change: 1 addition & 0 deletions docs/en/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ description: >
| `-a` | `--address` | Address of the interface the server will listen on. | `127.0.0.1` |
| | `--disable-reload` | Disables dynamic reloading config. | |
| `-h` | `--help` | help for toolbox | |
| | `--ignore-unknown-tools` | Log warnings and skip unknown/unsupported tool types instead of failing to start. | |
| | `--log-level` | Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'. | `info` |
| | `--logging-format` | Specify logging format to use. Allowed: 'standard' or 'JSON'. | `standard` |
| | `--mcp-prm-file` | Path to a manual Protected Resource Metadata (PRM) JSON file. If provided, overrides auto-generation for MCP Server-Wide Authentication. | |
Expand Down
13 changes: 13 additions & 0 deletions internal/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package server
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"regexp"
Expand Down Expand Up @@ -58,6 +59,8 @@ type ServerConfig struct {
PromptConfigs PromptConfigs
// PromptsetConfigs defines what prompts are available
PromptsetConfigs PromptsetConfigs
// IgnoreUnknownTools logs warnings and skips unknown/unsupported tool types instead of failing to start.
IgnoreUnknownTools bool
// LoggingFormat defines whether structured loggings are used.
LoggingFormat logFormat
// LogLevel defines the levels to log.
Expand Down Expand Up @@ -209,6 +212,9 @@ func UnmarshalResourceConfig(ctx context.Context, raw []byte) (SourceConfigs, Au
if err != nil {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("error unmarshaling %s: %s", kind, err)
}
if c == nil {
continue
}
if toolConfigs == nil {
toolConfigs = make(ToolConfigs)
}
Expand Down Expand Up @@ -405,6 +411,13 @@ func UnmarshalYAMLToolConfig(ctx context.Context, name string, r map[string]any)
}
toolCfg, err := tools.DecodeConfig(ctx, resourceType, name, dec)
if err != nil {
if errors.Is(err, tools.ErrUnknownToolType) && util.IgnoreUnknownToolsFromContext(ctx) {
l, logErr := util.LoggerFromContext(ctx)
if logErr == nil {
l.WarnContext(ctx, fmt.Sprintf("Skipping unknown tool type %q for tool %q", resourceType, name))
}
return nil, nil
}
return nil, err
}
return toolCfg, nil
Expand Down
13 changes: 13 additions & 0 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,19 @@ func InitializeConfigs(ctx context.Context, cfg ServerConfig) (
// initialize and validate the toolsets from configs
toolsetsMap := make(map[string]tools.Toolset)
for name, tc := range cfg.ToolsetConfigs {
if cfg.IgnoreUnknownTools {
filteredToolNames := make([]string, 0, len(tc.ToolNames))
for _, tn := range tc.ToolNames {
if _, ok := toolsMap[tn]; ok {
filteredToolNames = append(filteredToolNames, tn)
} else {
l.WarnContext(ctx, fmt.Sprintf("Skipping missing tool %q in toolset %q", tn, name))
}
}
tc.ToolNames = filteredToolNames
cfg.ToolsetConfigs[name] = tc
}
Comment thread
Yuan325 marked this conversation as resolved.

t, err := func() (tools.Toolset, error) {
_, span := instrumentation.Tracer.Start(
ctx,
Expand Down
4 changes: 3 additions & 1 deletion internal/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ func Register(resourceType string, factory ToolConfigFactory) bool {
return true
}

var ErrUnknownToolType = fmt.Errorf("unknown tool type")

// DecodeConfig looks up the registered factory for the given type and uses it
// to decode the tool configuration.
func DecodeConfig(ctx context.Context, resourceType string, name string, decoder *yaml.Decoder) (ToolConfig, error) {
factory, found := toolRegistry[resourceType]
if !found {
return nil, fmt.Errorf("unknown tool type: %q", resourceType)
return nil, fmt.Errorf("%w: %q", ErrUnknownToolType, resourceType)
}
toolConfig, err := factory(ctx, name, decoder)
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,18 @@ func ToolboxVersionFromContext(ctx context.Context) (string, error) {
return "", fmt.Errorf("unable to retrieve toolbox version")
}
}

const ignoreUnknownToolsKey contextKey = "ignoreUnknownTools"

// WithIgnoreUnknownTools adds the ignore-unknown-tools flag to the context
func WithIgnoreUnknownTools(ctx context.Context, ignore bool) context.Context {
return context.WithValue(ctx, ignoreUnknownToolsKey, ignore)
}

// IgnoreUnknownToolsFromContext retrieves the ignore-unknown-tools flag from context
func IgnoreUnknownToolsFromContext(ctx context.Context) bool {
if ignore, ok := ctx.Value(ignoreUnknownToolsKey).(bool); ok {
return ignore
}
return false
}
Loading