feat: add configs for external auth MCP usage + tool allow/denylist (#19794)

Closes https://github.com/coder/internal/issues/988

The logic for allowing/denying tools can be found in https://github.com/coder/aibridge/pull/4/files#diff-330a6371a583dd8cadeed79b95499e3a87960ad8ea4d6a94061e8f88a44834c3 (`ProxyBase.filterAllowedTools`).
This commit is contained in:
Danny Kopping
2025-09-16 20:31:29 +02:00
committed by GitHub
parent 655a36c392
commit 348a2e0285
11 changed files with 88 additions and 0 deletions
+6
View File
@@ -2722,6 +2722,12 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder
provider.DisplayName = v.Value
case "DISPLAY_ICON":
provider.DisplayIcon = v.Value
case "MCP_URL":
provider.MCPURL = v.Value
case "MCP_TOOL_ALLOW_REGEX":
provider.MCPToolAllowRegex = v.Value
case "MCP_TOOL_DENY_REGEX":
provider.MCPToolDenyRegex = v.Value
}
providers[providerNum] = provider
}
+9
View File
@@ -13306,6 +13306,15 @@ const docTemplate = `{
"description": "ID is a unique identifier for the auth config.\nIt defaults to ` + "`" + `type` + "`" + ` when not provided.",
"type": "string"
},
"mcp_tool_allow_regex": {
"type": "string"
},
"mcp_tool_deny_regex": {
"type": "string"
},
"mcp_url": {
"type": "string"
},
"no_refresh": {
"type": "boolean"
},
+9
View File
@@ -11942,6 +11942,15 @@
"description": "ID is a unique identifier for the auth config.\nIt defaults to `type` when not provided.",
"type": "string"
},
"mcp_tool_allow_regex": {
"type": "string"
},
"mcp_tool_deny_regex": {
"type": "string"
},
"mcp_url": {
"type": "string"
},
"no_refresh": {
"type": "boolean"
},
+31
View File
@@ -81,6 +81,19 @@ type Config struct {
// AppInstallationsURL is an API endpoint that returns a list of
// installations for the user. This is used for GitHub Apps.
AppInstallationsURL string
// MCPURL is the endpoint that clients must use to communicate with the associated
// MCP server.
MCPURL string
// MCPToolAllowRegex is a [regexp.Regexp] to match tools which are explicitly allowed to be
// injected into Coder AI Bridge upstream requests.
// In the case of conflicts, [MCPToolDenylistPattern] overrides items evaluated by this list.
// This field can be nil if unspecified in the config.
MCPToolAllowRegex *regexp.Regexp
// MCPToolDenyRegex is a [regexp.Regexp] to match tools which are explicitly NOT allowed to be
// injected into Coder AI Bridge upstream requests.
// In the case of conflicts, items evaluated by this list override [MCPToolAllowRegex].
// This field can be nil if unspecified in the config.
MCPToolDenyRegex *regexp.Regexp
}
// GenerateTokenExtra generates the extra token data to store in the database.
@@ -608,6 +621,21 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut
instrumented = instrument.NewGithub(entry.ID, oauthConfig)
}
var mcpToolAllow *regexp.Regexp
var mcpToolDeny *regexp.Regexp
if entry.MCPToolAllowRegex != "" {
mcpToolAllow, err = regexp.Compile(entry.MCPToolAllowRegex)
if err != nil {
return nil, xerrors.Errorf("compile MCP tool allow regex for external auth provider %q: %w", entry.ID, entry.MCPToolAllowRegex)
}
}
if entry.MCPToolDenyRegex != "" {
mcpToolDeny, err = regexp.Compile(entry.MCPToolDenyRegex)
if err != nil {
return nil, xerrors.Errorf("compile MCP tool deny regex for external auth provider %q: %w", entry.ID, entry.MCPToolDenyRegex)
}
}
cfg := &Config{
InstrumentedOAuth2Config: instrumented,
ID: entry.ID,
@@ -620,6 +648,9 @@ func ConvertConfig(instrument *promoauth.Factory, entries []codersdk.ExternalAut
DisplayName: entry.DisplayName,
DisplayIcon: entry.DisplayIcon,
ExtraTokenKeys: entry.ExtraTokenKeys,
MCPURL: entry.MCPURL,
MCPToolAllowRegex: mcpToolAllow,
MCPToolDenyRegex: mcpToolDeny,
}
if entry.DeviceFlow {
+3
View File
@@ -742,6 +742,9 @@ type ExternalAuthConfig struct {
ExtraTokenKeys []string `json:"-" yaml:"extra_token_keys"`
DeviceFlow bool `json:"device_flow" yaml:"device_flow"`
DeviceCodeURL string `json:"device_code_url" yaml:"device_code_url"`
MCPURL string `json:"mcp_url" yaml:"mcp_url"`
MCPToolAllowRegex string `json:"mcp_tool_allow_regex" yaml:"mcp_tool_allow_regex"`
MCPToolDenyRegex string `json:"mcp_tool_deny_regex" yaml:"mcp_tool_deny_regex"`
// Regex allows API requesters to match an auth config by
// a string (e.g. coder.com) instead of by it's type.
//
+3
View File
@@ -399,6 +399,9 @@ func TestExternalAuthYAMLConfig(t *testing.T) {
Regex: "^https://example.com/.*$",
DisplayName: "GitHub",
DisplayIcon: "/static/icons/github.svg",
MCPURL: "https://api.githubcopilot.com/mcp/",
MCPToolAllowRegex: ".*",
MCPToolDenyRegex: "create_gist",
}
// Input the github section twice for testing a slice of configs.
+3
View File
@@ -17,6 +17,9 @@ externalAuthProviders:
- token
device_flow: true
device_code_url: https://example.com/device
mcp_url: https://api.githubcopilot.com/mcp/
mcp_tool_allow_regex: .*
mcp_tool_deny_regex: create_gist
regex: ^https://example.com/.*$
display_name: GitHub
display_icon: /static/icons/github.svg
+3
View File
@@ -254,6 +254,9 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"display_icon": "string",
"display_name": "string",
"id": "string",
"mcp_tool_allow_regex": "string",
"mcp_tool_deny_regex": "string",
"mcp_url": "string",
"no_refresh": true,
"regex": "string",
"scopes": [
+15
View File
@@ -2358,6 +2358,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"display_icon": "string",
"display_name": "string",
"id": "string",
"mcp_tool_allow_regex": "string",
"mcp_tool_deny_regex": "string",
"mcp_url": "string",
"no_refresh": true,
"regex": "string",
"scopes": [
@@ -2859,6 +2862,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"display_icon": "string",
"display_name": "string",
"id": "string",
"mcp_tool_allow_regex": "string",
"mcp_tool_deny_regex": "string",
"mcp_url": "string",
"no_refresh": true,
"regex": "string",
"scopes": [
@@ -3562,6 +3568,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"display_icon": "string",
"display_name": "string",
"id": "string",
"mcp_tool_allow_regex": "string",
"mcp_tool_deny_regex": "string",
"mcp_url": "string",
"no_refresh": true,
"regex": "string",
"scopes": [
@@ -3586,6 +3595,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `display_icon` | string | false | | Display icon is a URL to an icon to display in the UI. |
| `display_name` | string | false | | Display name is shown in the UI to identify the auth config. |
| `id` | string | false | | ID is a unique identifier for the auth config. It defaults to `type` when not provided. |
| `mcp_tool_allow_regex` | string | false | | |
| `mcp_tool_deny_regex` | string | false | | |
| `mcp_url` | string | false | | |
| `no_refresh` | boolean | false | | |
|`regex`|string|false||Regex allows API requesters to match an auth config by a string (e.g. coder.com) instead of by it's type.
Git clone makes use of this by parsing the URL from: 'Username for "https://github.com":' And sending it to the Coder server to match against the Regex.|
@@ -12850,6 +12862,9 @@ None
"display_icon": "string",
"display_name": "string",
"id": "string",
"mcp_tool_allow_regex": "string",
"mcp_tool_deny_regex": "string",
"mcp_url": "string",
"no_refresh": true,
"regex": "string",
"scopes": [
+3
View File
@@ -1015,6 +1015,9 @@ export interface ExternalAuthConfig {
readonly scopes: readonly string[];
readonly device_flow: boolean;
readonly device_code_url: string;
readonly mcp_url: string;
readonly mcp_tool_allow_regex: string;
readonly mcp_tool_deny_regex: string;
readonly regex: string;
readonly display_name: string;
readonly display_icon: string;
@@ -23,6 +23,9 @@ const meta: Meta<typeof ExternalAuthSettingsPageView> = {
device_code_url: "",
display_icon: "",
display_name: "GitHub",
mcp_url: "",
mcp_tool_allow_regex: "",
mcp_tool_deny_regex: "",
},
],
},