diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden
index 7e7a7ece0d..49ce14b2f5 100644
--- a/cli/testdata/coder_server_--help.golden
+++ b/cli/testdata/coder_server_--help.golden
@@ -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.
diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden
index 225c240d9e..33f5c56c43 100644
--- a/cli/testdata/server-config.yaml.golden
+++ b/cli/testdata/server-config.yaml.golden
@@ -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
diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index ff4c39d7c9..9e61867557 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -11700,6 +11700,9 @@ const docTemplate = `{
"enabled": {
"type": "boolean"
},
+ "inject_coder_mcp_tools": {
+ "type": "boolean"
+ },
"openai": {
"$ref": "#/definitions/codersdk.AIBridgeOpenAIConfig"
}
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index a4c639d3fe..3d6e076ccc 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -10396,6 +10396,9 @@
"enabled": {
"type": "boolean"
},
+ "inject_coder_mcp_tools": {
+ "type": "boolean"
+ },
"openai": {
"$ref": "#/definitions/codersdk.AIBridgeOpenAIConfig"
}
diff --git a/codersdk/deployment.go b/codersdk/deployment.go
index 9425a3740f..4d79058b68 100644
--- a/codersdk/deployment.go
+++ b/codersdk/deployment.go
@@ -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. " +
@@ -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 {
diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md
index 5718979ae8..a0a24e6977 100644
--- a/docs/reference/api/general.md
+++ b/docs/reference/api/general.md
@@ -175,6 +175,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"small_fast_model": "string"
},
"enabled": true,
+ "inject_coder_mcp_tools": true,
"openai": {
"base_url": "string",
"key": "string"
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index e0b8a7e6c8..bdab37f297 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -389,6 +389,7 @@
"small_fast_model": "string"
},
"enabled": true,
+ "inject_coder_mcp_tools": true,
"openai": {
"base_url": "string",
"key": "string"
@@ -398,12 +399,13 @@
### Properties
-| Name | Type | Required | Restrictions | Description |
-|-------------|----------------------------------------------------------------------|----------|--------------|-------------|
-| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | |
-| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | |
-| `enabled` | boolean | false | | |
-| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | |
+| Name | Type | Required | Restrictions | Description |
+|--------------------------|----------------------------------------------------------------------|----------|--------------|-------------|
+| `anthropic` | [codersdk.AIBridgeAnthropicConfig](#codersdkaibridgeanthropicconfig) | false | | |
+| `bedrock` | [codersdk.AIBridgeBedrockConfig](#codersdkaibridgebedrockconfig) | false | | |
+| `enabled` | boolean | false | | |
+| `inject_coder_mcp_tools` | boolean | false | | |
+| `openai` | [codersdk.AIBridgeOpenAIConfig](#codersdkaibridgeopenaiconfig) | false | | |
## codersdk.AIBridgeInterception
@@ -695,6 +697,7 @@
"small_fast_model": "string"
},
"enabled": true,
+ "inject_coder_mcp_tools": true,
"openai": {
"base_url": "string",
"key": "string"
@@ -2851,6 +2854,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"small_fast_model": "string"
},
"enabled": true,
+ "inject_coder_mcp_tools": true,
"openai": {
"base_url": "string",
"key": "string"
@@ -3365,6 +3369,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"small_fast_model": "string"
},
"enabled": true,
+ "inject_coder_mcp_tools": true,
"openai": {
"base_url": "string",
"key": "string"
diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md
index e689f7fa28..bcebe05e7e 100644
--- a/docs/reference/cli/server.md
+++ b/docs/reference/cli/server.md
@@ -1752,3 +1752,14 @@ The model to use when making requests to the AWS Bedrock API.
| Default | global.anthropic.claude-haiku-4-5-20251001-v1:0 |
The small fast model to use when making requests to the AWS Bedrock API. Claude Code uses Haiku-class models to perform background tasks. See https://docs.claude.com/en/docs/claude-code/settings#environment-variables.
+
+### --aibridge-inject-coder-mcp-tools
+
+| | |
+|-------------|-----------------------------------------------------|
+| Type | bool |
+| Environment | $CODER_AIBRIDGE_INJECT_CODER_MCP_TOOLS |
+| YAML | 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).
diff --git a/enterprise/aibridgedserver/aibridgedserver.go b/enterprise/aibridgedserver/aibridgedserver.go
index 78939f39fb..156f3aa9d0 100644
--- a/enterprise/aibridgedserver/aibridgedserver.go
+++ b/enterprise/aibridgedserver/aibridgedserver.go
@@ -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 {
@@ -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) {
diff --git a/enterprise/aibridgedserver/aibridgedserver_test.go b/enterprise/aibridgedserver/aibridgedserver_test.go
index ff8c29d4d2..b871bfb3f8 100644
--- a/enterprise/aibridgedserver/aibridgedserver_test.go
+++ b/enterprise/aibridgedserver/aibridgedserver_test.go
@@ -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{
@@ -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)
@@ -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",
@@ -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 {
@@ -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)
@@ -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",
@@ -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)
diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden
index 492306c558..d272200609 100644
--- a/enterprise/cli/testdata/coder_server_--help.golden
+++ b/enterprise/cli/testdata/coder_server_--help.golden
@@ -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.
diff --git a/enterprise/coderd/aibridged.go b/enterprise/coderd/aibridged.go
index 285575df33..2ff2de902b 100644
--- a/enterprise/coderd/aibridged.go
+++ b/enterprise/coderd/aibridged.go
@@ -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
}
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 42374c80af..7b4ee1820c 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -31,6 +31,7 @@ export interface AIBridgeConfig {
readonly openai: AIBridgeOpenAIConfig;
readonly anthropic: AIBridgeAnthropicConfig;
readonly bedrock: AIBridgeBedrockConfig;
+ readonly inject_coder_mcp_tools: boolean;
}
// From codersdk/aibridge.go