Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite8b598e

Browse files
committed
feat(mcp): add experiment control for MCP server HTTP endpoints
- Add ExperimentMCPServerHTTP constant for controlled rollout- Refactor OAuth2 middleware into generic experiment middleware- Make experiment middleware variadic to support multiple experiments- Apply experiment gating to /api/experimental/mcp/http routes- Maintain development mode bypass for testing flexibility- Remove OAuth2-specific middleware in favor of reusable patternChange-Id: Ia5b3d0615f4a5a45e5a233b1ea92e8bdc0a5f17eSigned-off-by: Thomas Kosiewski <tk@coder.com>
1 parent15df5c5 commite8b598e

File tree

9 files changed

+93
-34
lines changed

9 files changed

+93
-34
lines changed

‎coderd/apidoc/docs.go‎

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json‎

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/coderd.go‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ func New(options *Options) *API {
922922
// logging into Coder with an external OAuth2 provider.
923923
r.Route("/oauth2",func(r chi.Router) {
924924
r.Use(
925-
api.oAuth2ProviderMiddleware,
925+
httpmw.RequireExperimentWithDevBypass(api.Experiments,codersdk.ExperimentOAuth2),
926926
)
927927
r.Route("/authorize",func(r chi.Router) {
928928
r.Use(
@@ -973,6 +973,9 @@ func New(options *Options) *API {
973973
r.Get("/prompts",api.aiTasksPrompts)
974974
})
975975
r.Route("/mcp",func(r chi.Router) {
976+
r.Use(
977+
httpmw.RequireExperimentWithDevBypass(api.Experiments,codersdk.ExperimentOAuth2,codersdk.ExperimentMCPServerHTTP),
978+
)
976979
// MCP HTTP transport endpoint with mandatory authentication
977980
r.Mount("/http",api.mcpHTTPHandler())
978981
})
@@ -1473,7 +1476,7 @@ func New(options *Options) *API {
14731476
r.Route("/oauth2-provider",func(r chi.Router) {
14741477
r.Use(
14751478
apiKeyMiddleware,
1476-
api.oAuth2ProviderMiddleware,
1479+
httpmw.RequireExperimentWithDevBypass(api.Experiments,codersdk.ExperimentOAuth2),
14771480
)
14781481
r.Route("/apps",func(r chi.Router) {
14791482
r.Get("/",api.oAuth2ProviderApps)

‎coderd/httpmw/experiments.go‎

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,59 @@ package httpmw
33
import (
44
"fmt"
55
"net/http"
6+
"strings"
67

8+
"github.com/coder/coder/v2/buildinfo"
79
"github.com/coder/coder/v2/coderd/httpapi"
810
"github.com/coder/coder/v2/codersdk"
911
)
1012

11-
funcRequireExperiment(experiments codersdk.Experiments,experiment codersdk.Experiment)func(next http.Handler) http.Handler {
13+
// RequireExperiment returns middleware that checks if all required experiments are enabled.
14+
// If any experiment is disabled, it returns a 403 Forbidden response with details about the missing experiments.
15+
funcRequireExperiment(experiments codersdk.Experiments,requiredExperiments...codersdk.Experiment)func(next http.Handler) http.Handler {
1216
returnfunc(next http.Handler) http.Handler {
1317
returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
14-
if!experiments.Enabled(experiment) {
15-
httpapi.Write(r.Context(),w,http.StatusForbidden, codersdk.Response{
16-
Message:fmt.Sprintf("Experiment '%s' is required but not enabled",experiment),
17-
})
18-
return
18+
for_,experiment:=rangerequiredExperiments {
19+
if!experiments.Enabled(experiment) {
20+
varexperimentNames []string
21+
for_,exp:=rangerequiredExperiments {
22+
experimentNames=append(experimentNames,string(exp))
23+
}
24+
25+
// Print a message that includes the experiment names
26+
// even if some experiments are already enabled.
27+
varmessagestring
28+
iflen(requiredExperiments)==1 {
29+
message=fmt.Sprintf("%s functionality requires enabling the '%s' experiment.",
30+
requiredExperiments[0].DisplayName(),requiredExperiments[0])
31+
}else {
32+
message=fmt.Sprintf("This functionality requires enabling the following experiments: %s",
33+
strings.Join(experimentNames,", "))
34+
}
35+
36+
httpapi.Write(r.Context(),w,http.StatusForbidden, codersdk.Response{
37+
Message:message,
38+
})
39+
return
40+
}
1941
}
42+
2043
next.ServeHTTP(w,r)
2144
})
2245
}
2346
}
47+
48+
// RequireExperimentWithDevBypass checks if ALL the given experiments are enabled,
49+
// but bypasses the check in development mode (buildinfo.IsDev()).
50+
funcRequireExperimentWithDevBypass(experiments codersdk.Experiments,requiredExperiments...codersdk.Experiment)func(next http.Handler) http.Handler {
51+
returnfunc(next http.Handler) http.Handler {
52+
returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
53+
ifbuildinfo.IsDev() {
54+
next.ServeHTTP(w,r)
55+
return
56+
}
57+
58+
RequireExperiment(experiments,requiredExperiments...)(next).ServeHTTP(w,r)
59+
})
60+
}
61+
}

‎coderd/oauth2.go‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
"github.com/sqlc-dev/pqtype"
1818

19-
"github.com/coder/coder/v2/buildinfo"
2019
"github.com/coder/coder/v2/coderd/audit"
2120
"github.com/coder/coder/v2/coderd/database"
2221
"github.com/coder/coder/v2/coderd/database/db2sdk"
@@ -37,19 +36,6 @@ const (
3736
displaySecretLength=6// Length of visible part in UI (last 6 characters)
3837
)
3938

40-
func (api*API)oAuth2ProviderMiddleware(next http.Handler) http.Handler {
41-
returnhttp.HandlerFunc(func(rw http.ResponseWriter,r*http.Request) {
42-
if!api.Experiments.Enabled(codersdk.ExperimentOAuth2)&&!buildinfo.IsDev() {
43-
httpapi.Write(r.Context(),rw,http.StatusForbidden, codersdk.Response{
44-
Message:"OAuth2 provider functionality requires enabling the 'oauth2' experiment.",
45-
})
46-
return
47-
}
48-
49-
next.ServeHTTP(rw,r)
50-
})
51-
}
52-
5339
// @Summary Get OAuth2 applications.
5440
// @ID get-oauth2-applications
5541
// @Security CoderSessionToken

‎codersdk/deployment.go‎

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
"github.com/google/uuid"
1818
"golang.org/x/mod/semver"
19+
"golang.org/x/text/cases"
20+
"golang.org/x/text/language"
1921
"golang.org/x/xerrors"
2022

2123
"github.com/coreos/go-oidc/v3/oidc"
@@ -3342,8 +3344,33 @@ const (
33423344
ExperimentWorkspaceUsageExperiment="workspace-usage"// Enables the new workspace usage tracking.
33433345
ExperimentWebPushExperiment="web-push"// Enables web push notifications through the browser.
33443346
ExperimentOAuth2Experiment="oauth2"// Enables OAuth2 provider functionality.
3347+
ExperimentMCPServerHTTPExperiment="mcp-server-http"// Enables the MCP HTTP server functionality.
33453348
)
33463349

3350+
func (eExperiment)DisplayName()string {
3351+
switche {
3352+
caseExperimentExample:
3353+
return"Example Experiment"
3354+
caseExperimentAutoFillParameters:
3355+
return"Auto-fill Template Parameters"
3356+
caseExperimentNotifications:
3357+
return"SMTP and Webhook Notifications"
3358+
caseExperimentWorkspaceUsage:
3359+
return"Workspace Usage Tracking"
3360+
caseExperimentWebPush:
3361+
return"Browser Push Notifications"
3362+
caseExperimentOAuth2:
3363+
return"OAuth2 Provider Functionality"
3364+
caseExperimentMCPServerHTTP:
3365+
return"MCP HTTP Server Functionality"
3366+
default:
3367+
// Split on hyphen and convert to title case
3368+
// e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http"
3369+
caser:=cases.Title(language.English)
3370+
returncaser.String(strings.ReplaceAll(string(e),"-"," "))
3371+
}
3372+
}
3373+
33473374
// ExperimentsKnown should include all experiments defined above.
33483375
varExperimentsKnown=Experiments{
33493376
ExperimentExample,
@@ -3352,6 +3379,7 @@ var ExperimentsKnown = Experiments{
33523379
ExperimentWorkspaceUsage,
33533380
ExperimentWebPush,
33543381
ExperimentOAuth2,
3382+
ExperimentMCPServerHTTP,
33553383
}
33563384

33573385
// ExperimentsSafe should include all experiments that are safe for
@@ -3369,14 +3397,9 @@ var ExperimentsSafe = Experiments{}
33693397
// @typescript-ignore Experiments
33703398
typeExperiments []Experiment
33713399

3372-
//Returns a list of experiments that are enabled for the deployment.
3400+
//Enabled returns a list of experiments that are enabled for the deployment.
33733401
func (eExperiments)Enabled(exExperiment)bool {
3374-
for_,v:=rangee {
3375-
ifv==ex {
3376-
returntrue
3377-
}
3378-
}
3379-
returnfalse
3402+
returnslices.Contains(e,ex)
33803403
}
33813404

33823405
func (c*Client)Experiments(ctx context.Context) (Experiments,error) {

‎docs/reference/api/schemas.md‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ require (
206206
golang.org/x/syncv0.14.0
207207
golang.org/x/sysv0.33.0
208208
golang.org/x/termv0.32.0
209-
golang.org/x/textv0.25.0// indirect
209+
golang.org/x/textv0.25.0
210210
golang.org/x/toolsv0.33.0
211211
golang.org/x/xerrorsv0.0.0-20240903120638-7835f813f4da
212212
google.golang.org/apiv0.231.0

‎site/src/api/typesGenerated.ts‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp