Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ee4cd13
feat(coderd): org-scope mcp server config schema
mafredri Jul 28, 2026
42bc247
feat: add mcp server config RBAC resource
mafredri Jul 29, 2026
ec3d920
feat(coderd): expose org MCP server APIs
mafredri Jul 21, 2026
f05991d
fix: cover MCP organization migration gaps
ibetitsmike Aug 7, 2026
ff4a1ff
fix(coderd): resolve MCP item routes via param middleware
ibetitsmike Aug 7, 2026
c7e66e8
feat(site): scope MCP servers to organizations
ibetitsmike Aug 7, 2026
03d5c07
fix(site): finish MCP organization cutover
ibetitsmike Aug 7, 2026
7506ba4
fix(coderd/database): align GetAuthorizedMCPServerConfigs with author…
ibetitsmike Aug 7, 2026
5076bb1
docs(docs/ai-coder): describe org-scoped MCP servers
ibetitsmike Aug 7, 2026
dde0e7d
chore: polish CODAGT-711 diff and drop default-org fallback remnants
ibetitsmike Aug 7, 2026
588c830
fix(coderd): keep MCP OAuth2 disconnect available to removed org members
ibetitsmike Aug 8, 2026
1c57159
fix(site): gate chat creation on the organization MCP server list
ibetitsmike Aug 8, 2026
58a5988
fix(coderd): stop copying MCP credentials across orgs and gate discov…
ibetitsmike Aug 8, 2026
710016c
fix(coderd/database/migrations): clear copied OAuth client identity f…
ibetitsmike Aug 10, 2026
d3dcc51
docs(coderd/database/migrations): document default-org credential han…
ibetitsmike Aug 10, 2026
9502b55
chore: apply cleanup gate round to MCP org-scope diff
ibetitsmike Aug 10, 2026
a3ca430
chore(coderd/database/migrations): renumber migration 000565 to 000567
ibetitsmike Aug 10, 2026
4f1ea85
fix(coderd/database): remap chat MCP config IDs written by pre-upgrad…
ibetitsmike Aug 11, 2026
87224c7
chore: apply cleanup gate round to upgrade-compat diff
ibetitsmike Aug 11, 2026
c3b60a7
chore(coderd/database/migrations): renumber migration 000567 to 000568
ibetitsmike Aug 11, 2026
55065a7
fix(coderd/x/chatd): seed computer-use MCP inheritance test in the ch…
ibetitsmike Aug 11, 2026
ca6d748
chore(coderd/database/migrations): renumber migration 000568 to 000569
ibetitsmike Aug 11, 2026
b1b2f02
fix(coderd/database): renumber MCP org migration to 000570
ibetitsmike Aug 13, 2026
0dc154d
fix(coderd): require deployment perms for user_oidc MCP configs
ibetitsmike Aug 13, 2026
d7f54b0
fix(coderd/database): keep dbcrypt no-auth MCP config copies enabled
ibetitsmike Aug 13, 2026
6ee6f38
fix(coderd): invalidate MCP user grants when config destinations change
ibetitsmike Aug 13, 2026
3ba9242
chore: apply cleanup gate findings to the MCP org scoping layer
ibetitsmike Aug 13, 2026
7f7e706
fix(coderd): keep chats sendable after a selected MCP server is disabled
ibetitsmike Aug 8, 2026
924b2c4
fix(coderd): merge MCP config updates onto the current row
ibetitsmike Aug 10, 2026
7301f3a
fix(site/src/pages/AgentsPage): keep cached MCP selections usable aft…
ibetitsmike Aug 10, 2026
4919aae
fix(coderd): preserve auditors' full MCP config visibility
ibetitsmike Aug 10, 2026
b8a1268
fix(site): use semantic textbox queries in MCP loading stories
ibetitsmike Aug 11, 2026
277f365
fix(coderd): invalidate MCP user grants when OAuth endpoints change
ibetitsmike Aug 14, 2026
500d08a
fix(coderd): reject MCP OAuth callbacks against superseded configs
ibetitsmike Aug 14, 2026
7809d6c
fix(coderd): lock MCP config row before invalidating OAuth grants
ibetitsmike Aug 14, 2026
985a725
chore(coderd): apply cleanup gate round to callback race fix
ibetitsmike Aug 14, 2026
d6b21ef
fix(enterprise/dbcrypt): decrypt MCP configs from the locking accessor
ibetitsmike Aug 14, 2026
a93b579
fix(coderd/database): keep existing MCP configs in the default organi…
ibetitsmike Aug 17, 2026
6f3529f
chore(coderd/database): apply cleanup gate to the no-copy migration diff
ibetitsmike Aug 17, 2026
e94b87e
fix(coderd/database): drop the rolling-upgrade chat remap trigger
ibetitsmike Aug 17, 2026
914f197
refactor: nest MCP server config routes under organizations
ibetitsmike Aug 17, 2026
bc6b425
refactor: filter enabled MCP server configs in SQL
ibetitsmike Aug 17, 2026
f710f2a
fix(coderd/database): drop defensive paths from migration 000570
ibetitsmike Aug 17, 2026
ca286ff
fix(site/src): scope MCP server API routes by organization
ibetitsmike Aug 17, 2026
dc5242d
chore(site/src/pages/AgentsPage): tighten the legacy MCP selection ho…
ibetitsmike Aug 17, 2026
d6f9a30
fix(coderd/database): renumber MCP org migration to 000571
ibetitsmike Aug 17, 2026
cbaedd0
fix(coderd): invalidate grants on revocation URL changes
ibetitsmike Aug 17, 2026
2def75f
fix(coderd): require config read for the full-view MCP list
ibetitsmike Aug 17, 2026
25d082e
fix(coderd): discover MCP OAuth before config creation
ibetitsmike Aug 17, 2026
43acab9
refactor(coderd): share MCP config insert path
ibetitsmike Aug 17, 2026
985aaeb
fix(coderd): avoid orphaned MCP OAuth clients
ibetitsmike Aug 18, 2026
bc64b4f
test(coderd): preserve MCP comment scope
ibetitsmike Aug 18, 2026
d5ae8c0
test(coderd): remove redundant MCP test comment
ibetitsmike Aug 18, 2026
3004c39
docs(coderd/x/chatd): add architecture TODO for MCP org scoping
ibetitsmike Aug 18, 2026
3b064b6
refactor(site/src/pages/AgentsPage): heal legacy MCP selection lazily…
ibetitsmike Aug 18, 2026
5f4b4da
fix(coderd/database): renumber MCP org migration to 000574
ibetitsmike Aug 18, 2026
caa6aea
fix(coderd/x/chatd): scope tool search test MCP configs to the chat org
ibetitsmike Aug 18, 2026
88d8e93
docs(docs/ai-coder/agents/platform-controls): note user_oidc needs de…
ibetitsmike Aug 18, 2026
14fd3a2
Revert "docs(coderd/x/chatd): add architecture TODO for MCP org scoping"
ibetitsmike Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 22 additions & 14 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,23 @@ func New(options *Options) *API {
r.Use(httpmw.RateLimit(options.FilesRateLimit, time.Minute))
r.Get("/chats/files/{file}/download", api.downloadChatFile)
})
r.Route("/organizations", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Route("/{organization}", func(r chi.Router) {
r.Use(httpmw.ExtractOrganizationParam(options.Database))
r.Route("/mcp-servers", func(r chi.Router) {
r.Get("/", api.listMCPServerConfigs)
r.Post("/", api.createMCPServerConfig)
Comment thread
ibetitsmike marked this conversation as resolved.
r.Route("/{mcpserverconfig}", func(r chi.Router) {
r.Use(httpmw.ExtractMCPServerConfigParam(options.Database))
r.Get("/", api.getMCPServerConfig)
r.Patch("/", api.updateMCPServerConfig)
r.Delete("/", api.deleteMCPServerConfig)
r.Get("/oauth2/connect", api.mcpServerOAuth2Connect)
})
})
})
})
r.Route("/chats", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
Expand Down Expand Up @@ -1499,20 +1516,11 @@ func New(options *Options) *API {
r.Use(
apiKeyMiddleware,
)
// MCP server configuration endpoints.
r.Route("/servers", func(r chi.Router) {
r.Get("/", api.listMCPServerConfigs)
r.Post("/", api.createMCPServerConfig)
r.Route("/{mcpServer}", func(r chi.Router) {
r.Get("/", api.getMCPServerConfig)
r.Patch("/", api.updateMCPServerConfig)
r.Delete("/", api.deleteMCPServerConfig)
// OAuth2 user flow
r.Get("/oauth2/connect", api.mcpServerOAuth2Connect)
r.Get("/oauth2/callback", api.mcpServerOAuth2Callback)
r.Delete("/oauth2/disconnect", api.mcpServerOAuth2Disconnect)
})
})
Comment thread
ibetitsmike marked this conversation as resolved.
// This callback path is frozen because it is registered with OAuth2 providers.
r.Get("/servers/{mcpServer}/oauth2/callback", api.mcpServerOAuth2Callback)
Comment thread
ibetitsmike marked this conversation as resolved.
// Disconnect stays outside organization routes so former organization
// members can delete their stored token after losing config read access.
r.Delete("/servers/{mcpServer}/oauth2/disconnect", api.mcpServerOAuth2Disconnect)
// MCP HTTP transport endpoint with mandatory authentication
r.Route("/http", func(r chi.Router) {
r.Use(httpmw.RequireExperimentWithDevBypass(api.Experiments, codersdk.ExperimentOAuth2, codersdk.ExperimentMCPServerHTTP))
Expand Down
76 changes: 45 additions & 31 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ var (
rbac.ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
rbac.ResourceWorkspace.Type: {policy.ActionRead, policy.ActionUpdate},
rbac.ResourceDeploymentConfig.Type: {policy.ActionRead},
rbac.ResourceMCPServerConfig.Type: {policy.ActionRead},
rbac.ResourceUser.Type: {policy.ActionReadPersonal},
}),
User: []rbac.Permission{},
Expand Down Expand Up @@ -2283,7 +2284,11 @@ func (q *querier) DeleteLicense(ctx context.Context, id int32) (int32, error) {
}

func (q *querier) DeleteMCPServerConfigByID(ctx context.Context, id uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
config, err := q.db.GetMCPServerConfigByID(ctx, id)
if err != nil {
return err
}
if err := q.authorizeContext(ctx, policy.ActionDelete, config); err != nil {
return err
}
return q.db.DeleteMCPServerConfigByID(ctx, id)
Expand All @@ -2296,6 +2301,17 @@ func (q *querier) DeleteMCPServerUserToken(ctx context.Context, arg database.Del
return q.db.DeleteMCPServerUserToken(ctx, arg)
}

func (q *querier) DeleteMCPServerUserTokensByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error {
config, err := q.db.GetMCPServerConfigByID(ctx, mcpServerConfigID)
if err != nil {
return err
}
if err := q.authorizeContext(ctx, policy.ActionUpdate, config); err != nil {
return err
}
return q.db.DeleteMCPServerUserTokensByConfigID(ctx, mcpServerConfigID)
}

func (q *querier) DeleteOAuth2ProviderAppByClientID(ctx context.Context, id uuid.UUID) error {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceOauth2App); err != nil {
return err
Expand Down Expand Up @@ -3763,11 +3779,12 @@ func (q *querier) GetEnabledChatModelConfigs(ctx context.Context) ([]database.Ge
return q.db.GetEnabledChatModelConfigs(ctx)
}

func (q *querier) GetEnabledMCPServerConfigs(ctx context.Context) ([]database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
return q.db.GetEnabledMCPServerConfigs(ctx)
func (q *querier) GetEnabledMCPServerConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.MCPServerConfig, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetEnabledMCPServerConfigsByOrganization)(ctx, organizationID)
}

func (q *querier) GetEnabledMCPServerConfigsByOrganizationAndIDs(ctx context.Context, arg database.GetEnabledMCPServerConfigsByOrganizationAndIDsParams) ([]database.MCPServerConfig, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetEnabledMCPServerConfigsByOrganizationAndIDs)(ctx, arg)
}

// GetExternalAgentTokensByTemplateID is used for scaletesting purposes; the
Expand Down Expand Up @@ -3842,11 +3859,8 @@ func (q *querier) GetFilteredInboxNotificationsByUserID(ctx context.Context, arg
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetFilteredInboxNotificationsByUserID)(ctx, arg)
}

func (q *querier) GetForcedMCPServerConfigs(ctx context.Context) ([]database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
return q.db.GetForcedMCPServerConfigs(ctx)
func (q *querier) GetForcedMCPServerConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.MCPServerConfig, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetForcedMCPServerConfigsByOrganization)(ctx, organizationID)
}

func (q *querier) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) {
Expand Down Expand Up @@ -4048,31 +4062,23 @@ func (q *querier) GetLogoURL(ctx context.Context) (string, error) {
}

func (q *querier) GetMCPServerConfigByID(ctx context.Context, id uuid.UUID) (database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return database.MCPServerConfig{}, err
}
return q.db.GetMCPServerConfigByID(ctx, id)
return fetch(q.log, q.auth, q.db.GetMCPServerConfigByID)(ctx, id)
}

func (q *querier) GetMCPServerConfigBySlug(ctx context.Context, slug string) (database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return database.MCPServerConfig{}, err
}
return q.db.GetMCPServerConfigBySlug(ctx, slug)
func (q *querier) GetMCPServerConfigByIDForUpdate(ctx context.Context, id uuid.UUID) (database.MCPServerConfig, error) {
return fetch(q.log, q.auth, q.db.GetMCPServerConfigByIDForUpdate)(ctx, id)
}

func (q *querier) GetMCPServerConfigs(ctx context.Context) ([]database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
}
return q.db.GetMCPServerConfigs(ctx)
func (q *querier) GetMCPServerConfigByOrganizationAndSlug(ctx context.Context, arg database.GetMCPServerConfigByOrganizationAndSlugParams) (database.MCPServerConfig, error) {
return fetch(q.log, q.auth, q.db.GetMCPServerConfigByOrganizationAndSlug)(ctx, arg)
}

func (q *querier) GetMCPServerConfigsByIDs(ctx context.Context, ids []uuid.UUID) ([]database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceDeploymentConfig); err != nil {
return nil, err
func (q *querier) GetMCPServerConfigsByOrganization(ctx context.Context, organizationID uuid.UUID) ([]database.MCPServerConfig, error) {
prepared, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceMCPServerConfig.Type)
if err != nil {
return nil, xerrors.Errorf("prepare sql filter: %w", err)
}
return q.db.GetMCPServerConfigsByIDs(ctx, ids)
return q.db.GetAuthorizedMCPServerConfigs(ctx, organizationID, prepared)
}

func (q *querier) GetMCPServerUserToken(ctx context.Context, arg database.GetMCPServerUserTokenParams) (database.MCPServerUserToken, error) {
Expand Down Expand Up @@ -6194,7 +6200,7 @@ func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseP
}

func (q *querier) InsertMCPServerConfig(ctx context.Context, arg database.InsertMCPServerConfigParams) (database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceMCPServerConfig.InOrg(arg.OrganizationID)); err != nil {
return database.MCPServerConfig{}, err
}
return q.db.InsertMCPServerConfig(ctx, arg)
Expand Down Expand Up @@ -7661,7 +7667,11 @@ func (q *querier) UpdateInboxNotificationReadStatus(ctx context.Context, args da
}

func (q *querier) UpdateMCPServerConfig(ctx context.Context, arg database.UpdateMCPServerConfigParams) (database.MCPServerConfig, error) {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
config, err := q.db.GetMCPServerConfigByID(ctx, arg.ID)
if err != nil {
return database.MCPServerConfig{}, err
}
if err := q.authorizeContext(ctx, policy.ActionUpdate, config); err != nil {
Comment thread
ibetitsmike marked this conversation as resolved.
return database.MCPServerConfig{}, err
}
return q.db.UpdateMCPServerConfig(ctx, arg)
Expand Down Expand Up @@ -9395,3 +9405,7 @@ func (q *querier) GetAuthorizedChats(ctx context.Context, arg database.GetChatsP
func (q *querier) GetAuthorizedChatsByChatFileID(ctx context.Context, fileID uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.Chat, error) {
return q.db.GetAuthorizedChatsByChatFileID(ctx, fileID, prepared)
}

func (q *querier) GetAuthorizedMCPServerConfigs(ctx context.Context, organizationID uuid.UUID, prepared rbac.PreparedAuthorized) ([]database.MCPServerConfig, error) {
return q.db.GetAuthorizedMCPServerConfigs(ctx, organizationID, prepared)
}
Loading
Loading