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
5 changes: 5 additions & 0 deletions cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ OPTIONS:
check is performed once per day.

AIBRIDGE OPTIONS:
--aibridge-inject-coder-mcp-tools bool, $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS (default: false)
Whether to inject Coder's MCP tools into intercepted AI Bridge
requests (requires the "oauth2" and "mcp-server-http" experiments to
be enabled).

--aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/)
The base URL of the Anthropic API.

Expand Down
4 changes: 4 additions & 0 deletions cli/testdata/server-config.yaml.golden
Original file line number Diff line number Diff line change
Expand Up @@ -747,3 +747,7 @@ aibridge:
# https://docs.claude.com/en/docs/claude-code/settings#environment-variables.
# (default: global.anthropic.claude-haiku-4-5-20251001-v1:0, type: string)
bedrock_small_fast_model: global.anthropic.claude-haiku-4-5-20251001-v1:0
# Whether to inject Coder's MCP tools into intercepted AI Bridge requests
# (requires the "oauth2" and "mcp-server-http" experiments to be enabled).
# (default: false, type: bool)
inject_coder_mcp_tools: false
3 changes: 3 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 15 additions & 4 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,16 @@ Write out the current server config as YAML to stdout.`,
Group: &deploymentGroupAIBridge,
YAML: "bedrock_small_fast_model",
},
{
Name: "AI Bridge Inject Coder MCP tools",
Description: "Whether to inject Coder's MCP tools into intercepted AI Bridge requests (requires the \"oauth2\" and \"mcp-server-http\" experiments to be enabled).",
Flag: "aibridge-inject-coder-mcp-tools",
Env: "CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS",
Value: &c.AI.BridgeConfig.InjectCoderMCPTools,
Default: "false",
Group: &deploymentGroupAIBridge,
YAML: "inject_coder_mcp_tools",
},
{
Name: "Enable Authorization Recordings",
Description: "All api requests will have a header including all authorization calls made during the request. " +
Expand All @@ -3358,10 +3368,11 @@ Write out the current server config as YAML to stdout.`,
}

type AIBridgeConfig struct {
Enabled serpent.Bool `json:"enabled" typescript:",notnull"`
OpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"`
Anthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"`
Bedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"`
Enabled serpent.Bool `json:"enabled" typescript:",notnull"`
OpenAI AIBridgeOpenAIConfig `json:"openai" typescript:",notnull"`
Anthropic AIBridgeAnthropicConfig `json:"anthropic" typescript:",notnull"`
Bedrock AIBridgeBedrockConfig `json:"bedrock" typescript:",notnull"`
InjectCoderMCPTools serpent.Bool `json:"inject_coder_mcp_tools" typescript:",notnull"`
}

type AIBridgeOpenAIConfig struct {
Expand Down
1 change: 1 addition & 0 deletions docs/reference/api/general.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 11 additions & 6 deletions docs/reference/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions docs/reference/cli/server.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 15 additions & 9 deletions enterprise/aibridgedserver/aibridgedserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ type Server struct {
coderMCPConfig *proto.MCPServerConfig // may be nil if not available
}

func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, accessURL string, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments) (*Server, error) {
func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, accessURL string,
bridgeCfg codersdk.AIBridgeConfig, externalAuthConfigs []*externalauth.Config, experiments codersdk.Experiments,
) (*Server, error) {
eac := make(map[string]*externalauth.Config, len(externalAuthConfigs))

for _, cfg := range externalAuthConfigs {
Expand All @@ -88,18 +90,22 @@ func NewServer(lifecycleCtx context.Context, store store, logger slog.Logger, ac
eac[cfg.ID] = cfg
}

coderMCPConfig, err := getCoderMCPServerConfig(experiments, accessURL)
if err != nil {
logger.Warn(lifecycleCtx, "failed to retrieve coder MCP server config, Coder MCP will not be available", slog.Error(err))
}

return &Server{
srv := &Server{
lifecycleCtx: lifecycleCtx,
store: store,
logger: logger.Named("aibridgedserver"),
externalAuthConfigs: eac,
coderMCPConfig: coderMCPConfig,
}, nil
}

if bridgeCfg.InjectCoderMCPTools {
coderMCPConfig, err := getCoderMCPServerConfig(experiments, accessURL)
if err != nil {
logger.Warn(lifecycleCtx, "failed to retrieve coder MCP server config, Coder MCP will not be available", slog.Error(err))
}
srv.coderMCPConfig = coderMCPConfig
}

return srv, nil
}

func (s *Server) RecordInterception(ctx context.Context, in *proto.RecordInterceptionRequest) (*proto.RecordInterceptionResponse, error) {
Expand Down
30 changes: 21 additions & 9 deletions enterprise/aibridgedserver/aibridgedserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/coder/coder/v2/enterprise/aibridged/proto"
"github.com/coder/coder/v2/enterprise/aibridgedserver"
"github.com/coder/coder/v2/testutil"
"github.com/coder/serpent"
)

var requiredExperiments = []codersdk.Experiment{
Expand Down Expand Up @@ -169,7 +170,7 @@ func TestAuthorization(t *testing.T) {
tc.mocksFn(db, apiKey, user)
}

srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", nil, requiredExperiments)
srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", codersdk.AIBridgeConfig{}, nil, requiredExperiments)
require.NoError(t, err)
require.NotNil(t, srv)

Expand Down Expand Up @@ -203,11 +204,12 @@ func TestGetMCPServerConfigs(t *testing.T) {
}

cases := []struct {
name string
experiments codersdk.Experiments
externalAuthConfigs []*externalauth.Config
expectCoderMCP bool
expectedExternalMCP bool
name string
disableCoderMCPInjection bool
experiments codersdk.Experiments
externalAuthConfigs []*externalauth.Config
expectCoderMCP bool
expectedExternalMCP bool
}{
{
name: "experiments not enabled",
Expand Down Expand Up @@ -238,6 +240,14 @@ func TestGetMCPServerConfigs(t *testing.T) {
expectCoderMCP: true,
expectedExternalMCP: true,
},
{
name: "both internal & external MCP, but coder MCP tools not injected",
disableCoderMCPInjection: true,
experiments: requiredExperiments,
externalAuthConfigs: externalAuthCfgs,
expectCoderMCP: false,
expectedExternalMCP: true,
},
}

for _, tc := range cases {
Expand All @@ -249,7 +259,9 @@ func TestGetMCPServerConfigs(t *testing.T) {
logger := testutil.Logger(t)

accessURL := "https://my-cool-deployment.com"
srv, err := aibridgedserver.NewServer(t.Context(), db, logger, accessURL, tc.externalAuthConfigs, tc.experiments)
srv, err := aibridgedserver.NewServer(t.Context(), db, logger, accessURL, codersdk.AIBridgeConfig{
InjectCoderMCPTools: serpent.Bool(!tc.disableCoderMCPInjection),
}, tc.externalAuthConfigs, tc.experiments)
require.NoError(t, err)
require.NotNil(t, srv)

Expand Down Expand Up @@ -287,7 +299,7 @@ func TestGetMCPServerAccessTokensBatch(t *testing.T) {
logger := testutil.Logger(t)

// Given: 2 external auth configured with MCP and 1 without.
srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", []*externalauth.Config{
srv, err := aibridgedserver.NewServer(t.Context(), db, logger, "/", codersdk.AIBridgeConfig{}, []*externalauth.Config{
{
ID: "1",
MCPURL: "1.com/mcp",
Expand Down Expand Up @@ -794,7 +806,7 @@ func testRecordMethod[Req any, Resp any](
}

ctx := testutil.Context(t, testutil.WaitLong)
srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", nil, requiredExperiments)
srv, err := aibridgedserver.NewServer(ctx, db, logger, "/", codersdk.AIBridgeConfig{}, nil, requiredExperiments)
require.NoError(t, err)

resp, err := callMethod(srv, ctx, tc.request)
Expand Down
5 changes: 5 additions & 0 deletions enterprise/cli/testdata/coder_server_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ OPTIONS:
check is performed once per day.

AIBRIDGE OPTIONS:
--aibridge-inject-coder-mcp-tools bool, $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS (default: false)
Whether to inject Coder's MCP tools into intercepted AI Bridge
requests (requires the "oauth2" and "mcp-server-http" experiments to
be enabled).

--aibridge-anthropic-base-url string, $CODER_AIBRIDGE_ANTHROPIC_BASE_URL (default: https://api.anthropic.com/)
The base URL of the Anthropic API.

Expand Down
2 changes: 1 addition & 1 deletion enterprise/coderd/aibridged.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (api *API) CreateInMemoryAIBridgeServer(dialCtx context.Context) (client ai

mux := drpcmux.New()
srv, err := aibridgedserver.NewServer(api.ctx, api.Database, api.Logger.Named("aibridgedserver"),
api.AccessURL.String(), api.ExternalAuthConfigs, api.AGPL.Experiments)
api.AccessURL.String(), api.DeploymentValues.AI.BridgeConfig, api.ExternalAuthConfigs, api.AGPL.Experiments)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions site/src/api/typesGenerated.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading