@Summary Create MCP server config @x-apidocgen {"skip": true} EXPERIMENTAL: this endpoint is experimental and is subject to change. nolint:revive // HTTP handler writes to ResponseWriter.
(rw http.ResponseWriter, r *http.Request)
| 223 | // |
| 224 | //nolint:revive // HTTP handler writes to ResponseWriter. |
| 225 | func (api *API) createMCPServerConfig(rw http.ResponseWriter, r *http.Request) { |
| 226 | ctx := r.Context() |
| 227 | apiKey := httpmw.APIKey(r) |
| 228 | if !api.Authorize(r, policy.ActionUpdate, rbac.ResourceDeploymentConfig) { |
| 229 | httpapi.Forbidden(rw) |
| 230 | return |
| 231 | } |
| 232 | |
| 233 | var req codersdk.CreateMCPServerConfigRequest |
| 234 | if !httpapi.Read(ctx, rw, r, &req) { |
| 235 | return |
| 236 | } |
| 237 | |
| 238 | // Validate auth-type-dependent fields. |
| 239 | switch req.AuthType { |
| 240 | case "oauth2": |
| 241 | // When the admin does not provide OAuth2 credentials, attempt |
| 242 | // automatic discovery and Dynamic Client Registration (RFC 7591) |
| 243 | // using the MCP server URL. This follows the MCP authorization |
| 244 | // spec: discover the authorization server via Protected Resource |
| 245 | // Metadata (RFC 9728) and Authorization Server Metadata |
| 246 | // (RFC 8414), then register a client dynamically. |
| 247 | if req.OAuth2ClientID == "" && req.OAuth2AuthURL == "" && req.OAuth2TokenURL == "" { |
| 248 | // Auto-discovery flow: we need the config ID first to |
| 249 | // build the correct callback URL. Insert the record |
| 250 | // with empty OAuth2 fields, perform discovery, then |
| 251 | // update. |
| 252 | customHeadersJSON, err := marshalCustomHeaders(req.CustomHeaders) |
| 253 | if err != nil { |
| 254 | httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ |
| 255 | Message: "Invalid custom headers.", |
| 256 | Detail: err.Error(), |
| 257 | }) |
| 258 | return |
| 259 | } |
| 260 | |
| 261 | inserted, err := api.Database.InsertMCPServerConfig(ctx, database.InsertMCPServerConfigParams{ |
| 262 | DisplayName: strings.TrimSpace(req.DisplayName), |
| 263 | Slug: strings.TrimSpace(req.Slug), |
| 264 | Description: strings.TrimSpace(req.Description), |
| 265 | IconURL: strings.TrimSpace(req.IconURL), |
| 266 | Transport: strings.TrimSpace(req.Transport), |
| 267 | Url: strings.TrimSpace(req.URL), |
| 268 | AuthType: strings.TrimSpace(req.AuthType), |
| 269 | OAuth2ClientID: "", |
| 270 | OAuth2ClientSecret: "", |
| 271 | OAuth2ClientSecretKeyID: sql.NullString{}, |
| 272 | OAuth2AuthURL: "", |
| 273 | OAuth2TokenURL: "", |
| 274 | OAuth2Scopes: "", |
| 275 | APIKeyHeader: strings.TrimSpace(req.APIKeyHeader), |
| 276 | APIKeyValue: strings.TrimSpace(req.APIKeyValue), |
| 277 | APIKeyValueKeyID: sql.NullString{}, |
| 278 | CustomHeaders: customHeadersJSON, |
| 279 | CustomHeadersKeyID: sql.NullString{}, |
| 280 | ToolAllowList: coalesceStringSlice(trimStringSlice(req.ToolAllowList)), |
| 281 | ToolDenyList: coalesceStringSlice(trimStringSlice(req.ToolDenyList)), |
| 282 | Availability: strings.TrimSpace(req.Availability), |
no test coverage detected