diff --git a/cli/server.go b/cli/server.go index 097dd9460c..554ab5331b 100644 --- a/cli/server.go +++ b/cli/server.go @@ -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 } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 2f45203b6b..bab85a179a 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -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" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index b94b2307d3..f218fcdafc 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -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" }, diff --git a/coderd/externalauth/externalauth.go b/coderd/externalauth/externalauth.go index 24ebe13d03..55dba499e6 100644 --- a/coderd/externalauth/externalauth.go +++ b/coderd/externalauth/externalauth.go @@ -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 { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c86297307f..47ae4f51c1 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -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. // diff --git a/codersdk/deployment_test.go b/codersdk/deployment_test.go index c113d46cc5..fc8f532d8f 100644 --- a/codersdk/deployment_test.go +++ b/codersdk/deployment_test.go @@ -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. diff --git a/codersdk/testdata/githubcfg.yaml b/codersdk/testdata/githubcfg.yaml index 50c16fb30f..c88cea947d 100644 --- a/codersdk/testdata/githubcfg.yaml +++ b/codersdk/testdata/githubcfg.yaml @@ -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 diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index 925e74a022..eab4e5e417 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -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": [ diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index f8e0ea267f..30b1bda42d 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -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": [ diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 9f10630ab9..c1e703848e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -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; diff --git a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx index 5184219b38..f44ecd4144 100644 --- a/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/ExternalAuthSettingsPage/ExternalAuthSettingsPageView.stories.tsx @@ -23,6 +23,9 @@ const meta: Meta = { device_code_url: "", display_icon: "", display_name: "GitHub", + mcp_url: "", + mcp_tool_allow_regex: "", + mcp_tool_deny_regex: "", }, ], },