// Copyright © 2025, Oracle and/or its affiliates. package oraclesql import ( "context" "database/sql" "fmt" "net/http" yaml "github.com/goccy/go-yaml" "github.com/googleapis/mcp-toolbox/internal/tools" "github.com/googleapis/mcp-toolbox/internal/util" "github.com/googleapis/mcp-toolbox/internal/util/parameters" ) const resourceType string = "oracle-sql" func init() { if !tools.Register(resourceType, newConfig) { panic(fmt.Sprintf("tool type %q already registered", resourceType)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{ConfigBase: tools.ConfigBase{Name: name}} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { OracleDB() *sql.DB RunSQL(context.Context, string, []any, bool) (any, error) } type Config struct { tools.ConfigBase `yaml:",inline"` Type string `yaml:"type" validate:"required"` Source string `yaml:"source" validate:"required"` Statement string `yaml:"statement" validate:"required"` ReadOnly *bool `yaml:"readOnly"` Parameters parameters.Parameters `yaml:"parameters"` TemplateParameters parameters.Parameters `yaml:"templateParameters"` Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigType() string { return resourceType } func (cfg Config) Initialize(context.Context) (tools.Tool, error) { if cfg.Description == "" { return nil, fmt.Errorf("description is required for tool %q", cfg.Name) } allParameters, paramManifest, err := parameters.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) if err != nil { return nil, fmt.Errorf("error processing parameters: %w", err) } return Tool{ BaseTool: tools.NewBaseTool( cfg, tools.GetAnnotationsOrDefault(cfg.Annotations, tools.NewDestructiveAnnotations), tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, allParameters, ), }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { tools.BaseTool[Config] } func (t Tool) ToConfig() tools.ToolConfig { return t.Cfg } func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) { source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Cfg.Source, t.Cfg.Name, t.Cfg.Type) if err != nil { return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err) } paramsMap := params.AsMap() newStatement, err := parameters.ResolveTemplateParams(t.Cfg.TemplateParameters, t.Cfg.Statement, paramsMap) if err != nil { return nil, util.NewAgentError("unable to extract template params", err) } newParams, err := parameters.GetParams(t.Cfg.Parameters, paramsMap) if err != nil { return nil, util.NewAgentError("unable to extract standard params", err) } sliceParams := newParams.AsSlice() logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, util.NewClientServerError("error getting logger", http.StatusInternalServerError, err) } logger.DebugContext(ctx, "executing %s tool query: %s", resourceType, newStatement) isReadOnly := true if t.Cfg.ReadOnly != nil { isReadOnly = *t.Cfg.ReadOnly } resp, err := source.RunSQL(ctx, newStatement, sliceParams, isReadOnly) if err != nil { return nil, util.ProcessGeneralError(err) } return resp, nil }