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
17 changes: 9 additions & 8 deletions core/mcp/clientmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,15 @@ func (m *MCPManager) UpdateClient(id string, updatedConfig *schemas.MCPClientCon
ConfigHash: client.ExecutionConfig.ConfigHash,
ToolPricing: maps.Clone(client.ExecutionConfig.ToolPricing),
// Updatable fields - copy from updated config with proper cloning
Name: updatedConfig.Name,
IsCodeModeClient: updatedConfig.IsCodeModeClient,
Headers: maps.Clone(updatedConfig.Headers),
ToolsToExecute: slices.Clone(updatedConfig.ToolsToExecute),
ToolsToAutoExecute: slices.Clone(updatedConfig.ToolsToAutoExecute),
AllowedExtraHeaders: slices.Clone(updatedConfig.AllowedExtraHeaders),
IsPingAvailable: updatedConfig.IsPingAvailable,
ToolSyncInterval: updatedConfig.ToolSyncInterval,
Name: updatedConfig.Name,
IsCodeModeClient: updatedConfig.IsCodeModeClient,
Headers: maps.Clone(updatedConfig.Headers),
ToolsToExecute: slices.Clone(updatedConfig.ToolsToExecute),
ToolsToAutoExecute: slices.Clone(updatedConfig.ToolsToAutoExecute),
AllowedExtraHeaders: slices.Clone(updatedConfig.AllowedExtraHeaders),
IsPingAvailable: updatedConfig.IsPingAvailable,
ToolSyncInterval: updatedConfig.ToolSyncInterval,
AllowOnAllVirtualKeys: updatedConfig.AllowOnAllVirtualKeys,
}

// Atomically replace the config pointer
Expand Down
9 changes: 5 additions & 4 deletions core/schemas/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ type MCPClientConfig struct {
// - nil/omitted => treated as [] (no tools)
// - ["tool1", "tool2"] => auto-execute only the specified tools
// Note: If a tool is in ToolsToAutoExecute but not in ToolsToExecute, it will be skipped.
IsPingAvailable *bool `json:"is_ping_available,omitempty"` // Whether the MCP server supports ping for health checks (nil/true = ping; false = listTools). Defaults to true.
ToolSyncInterval time.Duration `json:"tool_sync_interval,omitempty"` // Per-client override for tool sync interval (0 = use global, negative = disabled)
ToolPricing map[string]float64 `json:"tool_pricing,omitempty"` // Tool pricing for each tool (cost per execution)
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
IsPingAvailable *bool `json:"is_ping_available,omitempty"` // Whether the MCP server supports ping for health checks (nil/true = ping; false = listTools). Defaults to true.
ToolSyncInterval time.Duration `json:"tool_sync_interval,omitempty"` // Per-client override for tool sync interval (0 = use global, negative = disabled)
ToolPricing map[string]float64 `json:"tool_pricing,omitempty"` // Tool pricing for each tool (cost per execution)
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
AllowOnAllVirtualKeys bool `json:"allow_on_all_virtual_keys"` // Whether to allow the MCP client to run on all virtual keys
}

// NewMCPClientConfigFromMap creates a new MCP client config from a map[string]any.
Expand Down
21 changes: 21 additions & 0 deletions docs/openapi/schemas/management/mcp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ MCPClientCreateRequestBase:
["*"] => all executable tools can be auto-executed
[] => no tools are auto-executed
["tool1", "tool2"] => only specified tools can be auto-executed
allow_on_all_virtual_keys:
type: boolean
default: false
description: |
When true, this MCP client's tools are available to all virtual keys by default,
without requiring an explicit virtual key assignment.
An explicit virtual key config always overrides this setting for that key.
MCPClientCreateRequestHTTP:
allOf:
- $ref: '#/MCPClientCreateRequestBase'
Expand Down Expand Up @@ -227,6 +234,13 @@ MCPClientUpdateRequest:
Key is the tool name, value is the cost per execution.
Example: {"read_file": 0.001, "write_file": 0.002}
Note: Only available when updating an existing client after tools have been fetched.
allow_on_all_virtual_keys:
type: boolean
default: false
description: |
When true, this MCP client's tools are accessible to all virtual keys without requiring
explicit per-key assignment. All tools are allowed by default. If a virtual key has an
explicit MCP config for this client, that config takes precedence and overrides this behaviour.
vk_configs:
type: array
items:
Expand Down Expand Up @@ -320,6 +334,13 @@ MCPClientConfig:
Per-tool cost in USD for execution.
Key is the tool name, value is the cost per execution.
Example: {"read_file": 0.001, "write_file": 0.002}
allow_on_all_virtual_keys:
type: boolean
default: false
description: |
When true, this MCP client's tools are accessible to all virtual keys without requiring
explicit per-key assignment. All tools are allowed by default. If a virtual key has an
explicit MCP config for this client, that config takes precedence and overrides this behaviour.

ChatToolFunction:
type: object
Expand Down
1 change: 1 addition & 0 deletions framework/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
- feat: add MCPDisableAutoToolInject column to TableClientConfig
- refactor: standardize empty array conventions in modelcatalog and tables.
- feat: add AllowedExtraHeadersJSON column to TableMCPClient
- feat: add AllowOnAllVirtualKeys column to TableMCPClient
33 changes: 33 additions & 0 deletions framework/configstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
if err := migrationMakeBasePricingColumnsNullable(ctx, db); err != nil {
return err
}
if err := migrationAddAllowOnAllVirtualKeysColumn(ctx, db); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -5202,3 +5205,33 @@ func migrationMakeBasePricingColumnsNullable(ctx context.Context, db *gorm.DB) e
}
return nil
}

func migrationAddAllowOnAllVirtualKeysColumn(ctx context.Context, db *gorm.DB) error {
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
ID: "add_allow_on_all_virtual_keys_column",
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
migrator := tx.Migrator()
if !migrator.HasColumn(&tables.TableMCPClient{}, "allow_on_all_virtual_keys") {
if err := migrator.AddColumn(&tables.TableMCPClient{}, "allow_on_all_virtual_keys"); err != nil {
return fmt.Errorf("failed to add allow_on_all_virtual_keys column: %w", err)
}
}
return nil
},
Rollback: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
migrator := tx.Migrator()
if migrator.HasColumn(&tables.TableMCPClient{}, "allow_on_all_virtual_keys") {
if err := migrator.DropColumn(&tables.TableMCPClient{}, "allow_on_all_virtual_keys"); err != nil {
return fmt.Errorf("failed to drop allow_on_all_virtual_keys column: %w", err)
}
}
return nil
},
}})
if err := m.Migrate(); err != nil {
return fmt.Errorf("error while running add_allow_on_all_virtual_keys_column migration: %s", err.Error())
}
return nil
}
92 changes: 48 additions & 44 deletions framework/configstore/rdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,21 +866,22 @@ func (s *RDBConfigStore) GetMCPConfig(ctx context.Context) (*schemas.MCPConfig,
clientConfigs := make([]*schemas.MCPClientConfig, len(dbMCPClients))
for i, dbClient := range dbMCPClients {
clientConfigs[i] = &schemas.MCPClientConfig{
ID: dbClient.ClientID,
Name: dbClient.Name,
IsCodeModeClient: dbClient.IsCodeModeClient,
ConnectionType: schemas.MCPConnectionType(dbClient.ConnectionType),
ConnectionString: dbClient.ConnectionString,
StdioConfig: dbClient.StdioConfig,
AuthType: schemas.MCPAuthType(dbClient.AuthType),
OauthConfigID: dbClient.OauthConfigID,
ToolsToExecute: dbClient.ToolsToExecute,
ToolsToAutoExecute: dbClient.ToolsToAutoExecute,
Headers: dbClient.Headers,
AllowedExtraHeaders: dbClient.AllowedExtraHeaders,
IsPingAvailable: dbClient.IsPingAvailable,
ToolSyncInterval: time.Duration(dbClient.ToolSyncInterval) * time.Minute,
ToolPricing: dbClient.ToolPricing,
ID: dbClient.ClientID,
Name: dbClient.Name,
IsCodeModeClient: dbClient.IsCodeModeClient,
ConnectionType: schemas.MCPConnectionType(dbClient.ConnectionType),
ConnectionString: dbClient.ConnectionString,
StdioConfig: dbClient.StdioConfig,
AuthType: schemas.MCPAuthType(dbClient.AuthType),
OauthConfigID: dbClient.OauthConfigID,
ToolsToExecute: dbClient.ToolsToExecute,
ToolsToAutoExecute: dbClient.ToolsToAutoExecute,
Headers: dbClient.Headers,
AllowedExtraHeaders: dbClient.AllowedExtraHeaders,
IsPingAvailable: dbClient.IsPingAvailable,
ToolSyncInterval: time.Duration(dbClient.ToolSyncInterval) * time.Minute,
ToolPricing: dbClient.ToolPricing,
AllowOnAllVirtualKeys: dbClient.AllowOnAllVirtualKeys,
}
}
return &schemas.MCPConfig{
Expand All @@ -902,21 +903,22 @@ func (s *RDBConfigStore) GetMCPConfig(ctx context.Context) (*schemas.MCPConfig,
clientConfigs := make([]*schemas.MCPClientConfig, len(dbMCPClients))
for i, dbClient := range dbMCPClients {
clientConfigs[i] = &schemas.MCPClientConfig{
ID: dbClient.ClientID,
Name: dbClient.Name,
IsCodeModeClient: dbClient.IsCodeModeClient,
ConnectionType: schemas.MCPConnectionType(dbClient.ConnectionType),
ConnectionString: dbClient.ConnectionString,
StdioConfig: dbClient.StdioConfig,
AuthType: schemas.MCPAuthType(dbClient.AuthType),
OauthConfigID: dbClient.OauthConfigID,
ToolsToExecute: dbClient.ToolsToExecute,
ToolsToAutoExecute: dbClient.ToolsToAutoExecute,
Headers: dbClient.Headers,
AllowedExtraHeaders: dbClient.AllowedExtraHeaders,
IsPingAvailable: dbClient.IsPingAvailable,
ToolSyncInterval: time.Duration(dbClient.ToolSyncInterval) * time.Minute,
ToolPricing: dbClient.ToolPricing,
ID: dbClient.ClientID,
Name: dbClient.Name,
IsCodeModeClient: dbClient.IsCodeModeClient,
ConnectionType: schemas.MCPConnectionType(dbClient.ConnectionType),
ConnectionString: dbClient.ConnectionString,
StdioConfig: dbClient.StdioConfig,
AuthType: schemas.MCPAuthType(dbClient.AuthType),
OauthConfigID: dbClient.OauthConfigID,
ToolsToExecute: dbClient.ToolsToExecute,
ToolsToAutoExecute: dbClient.ToolsToAutoExecute,
Headers: dbClient.Headers,
AllowedExtraHeaders: dbClient.AllowedExtraHeaders,
IsPingAvailable: dbClient.IsPingAvailable,
ToolSyncInterval: time.Duration(dbClient.ToolSyncInterval) * time.Minute,
AllowOnAllVirtualKeys: dbClient.AllowOnAllVirtualKeys,
ToolPricing: dbClient.ToolPricing,
}
}
return &schemas.MCPConfig{
Expand Down Expand Up @@ -1001,20 +1003,21 @@ func (s *RDBConfigStore) CreateMCPClientConfig(ctx context.Context, clientConfig
}
// Create new client
dbClient := tables.TableMCPClient{
ClientID: clientConfigCopy.ID,
Name: clientConfigCopy.Name,
IsCodeModeClient: clientConfigCopy.IsCodeModeClient,
ConnectionType: string(clientConfigCopy.ConnectionType),
ConnectionString: clientConfigCopy.ConnectionString,
StdioConfig: clientConfigCopy.StdioConfig,
AuthType: string(clientConfigCopy.AuthType),
OauthConfigID: clientConfigCopy.OauthConfigID,
ToolsToExecute: clientConfigCopy.ToolsToExecute,
ToolsToAutoExecute: clientConfigCopy.ToolsToAutoExecute,
Headers: clientConfigCopy.Headers,
AllowedExtraHeaders: clientConfigCopy.AllowedExtraHeaders,
IsPingAvailable: clientConfigCopy.IsPingAvailable,
ToolSyncInterval: int(clientConfigCopy.ToolSyncInterval.Minutes()),
ClientID: clientConfigCopy.ID,
Name: clientConfigCopy.Name,
IsCodeModeClient: clientConfigCopy.IsCodeModeClient,
ConnectionType: string(clientConfigCopy.ConnectionType),
ConnectionString: clientConfigCopy.ConnectionString,
StdioConfig: clientConfigCopy.StdioConfig,
AuthType: string(clientConfigCopy.AuthType),
OauthConfigID: clientConfigCopy.OauthConfigID,
ToolsToExecute: clientConfigCopy.ToolsToExecute,
ToolsToAutoExecute: clientConfigCopy.ToolsToAutoExecute,
Headers: clientConfigCopy.Headers,
AllowedExtraHeaders: clientConfigCopy.AllowedExtraHeaders,
IsPingAvailable: clientConfigCopy.IsPingAvailable,
ToolSyncInterval: int(clientConfigCopy.ToolSyncInterval.Minutes()),
AllowOnAllVirtualKeys: clientConfigCopy.AllowOnAllVirtualKeys,
}
if err := tx.WithContext(ctx).Create(&dbClient).Error; err != nil {
return s.parseGormError(err)
Expand Down Expand Up @@ -1109,6 +1112,7 @@ func (s *RDBConfigStore) UpdateMCPClientConfig(ctx context.Context, id string, c
"allowed_extra_headers_json": string(allowedExtraHeadersJSON),
"tool_pricing_json": string(toolPricingJSON),
"tool_sync_interval": clientConfigCopy.ToolSyncInterval,
"allow_on_all_virtual_keys": clientConfigCopy.AllowOnAllVirtualKeys,
"updated_at": time.Now(),
}
if encrypt.IsEnabled() {
Expand Down
2 changes: 2 additions & 0 deletions framework/configstore/tables/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type TableMCPClient struct {
OauthConfigID *string `gorm:"type:varchar(255);index;constraint:OnDelete:CASCADE" json:"oauth_config_id"` // Foreign key to oauth_configs.ID with CASCADE delete
OauthConfig *TableOauthConfig `gorm:"foreignKey:OauthConfigID;references:ID;constraint:OnDelete:CASCADE" json:"-"` // Gorm relationship

AllowOnAllVirtualKeys bool `gorm:"default:false" json:"allow_on_all_virtual_keys"` // Whether to allow the MCP client to run on all virtual keys

// Config hash is used to detect the changes synced from config.json file
// Every time we sync the config.json file, we will update the config hash
ConfigHash string `gorm:"type:varchar(255);null" json:"config_hash"`
Expand Down
Loading
Loading