mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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
|
||||
}
|
||||
|
||||
Generated
+9
@@ -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"
|
||||
},
|
||||
|
||||
Generated
+9
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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.
|
||||
|
||||
Vendored
+3
@@ -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
|
||||
|
||||
Generated
+3
@@ -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": [
|
||||
|
||||
Generated
+15
@@ -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": [
|
||||
|
||||
Generated
+3
@@ -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;
|
||||
|
||||
+3
@@ -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: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user