Skip to content

Commit 95a10bd

Browse files
remyleoneCopilotestellesoulard
authored
fix(mcp): fix support for streamable http (#5554)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: esoulard <esoulard@scaleway.com>
1 parent 32eb8b2 commit 95a10bd

41 files changed

Lines changed: 1657 additions & 1126 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/scw/testdata/test-all-usage-mcp-server-list-resources-usage.golden

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ EXAMPLES:
1010
scw mcp server list-resources
1111

1212
List resources for a specific namespace
13-
scw mcp server list-resources namespace=instance
13+
scw mcp server list-resources namespaces=instance
1414

1515
List resources for a specific resource type
16-
scw mcp server list-resources resource=server
16+
scw mcp server list-resources resources=server
1717

1818
List only read-only resources
1919
scw mcp server list-resources read-only=true
2020

2121
ARGS:
22-
[namespace] Filter by namespace (e.g., instance, iam, object)
23-
[resource] Filter by resource (e.g., server, volume, bucket)
22+
[namespaces] Filter by namespaces (e.g., instance, iam, object)
23+
[resources] Filter by resources (e.g., server, volume, bucket)
2424
[read-only=false] Only list read-only resources
2525

2626
FLAGS:

cmd/scw/testdata/test-all-usage-mcp-server-list-tools-usage.golden

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@ EXAMPLES:
99
List all available MCP tools
1010
scw mcp server list-tools
1111

12-
List tools for a specific namespace
13-
scw mcp server list-tools namespace=instance
12+
List tools for specific namespaces (comma-separated)
13+
scw mcp server list-tools namespaces=instance
1414

15-
List tools for a specific resource
16-
scw mcp server list-tools resource=server
15+
List tools for specific resources (comma-separated)
16+
scw mcp server list-tools resources=server
1717

1818
List only read-only tools (get/list operations)
1919
scw mcp server list-tools read-only=true
2020

21-
List tools with a specific verb
22-
scw mcp server list-tools verb=get
21+
List tools with specific verbs (comma-separated)
22+
scw mcp server list-tools verbs=get,list
2323

2424
ARGS:
25-
[namespace] Filter by namespace (e.g., instance, iam, object)
26-
[resource] Filter by resource (e.g., server, volume, bucket)
27-
[verb] Filter by verb (e.g., get, list, create)
25+
[namespaces] Filter by namespaces (e.g., instance, iam, object)
26+
[resources] Filter by resources (e.g., server, volume, bucket)
27+
[verbs] Filter by verbs (e.g., get, list, create)
2828
[read-only=false] Only list read-only tools (get, list operations)
2929

3030
FLAGS:

cmd/scw/testdata/test-all-usage-mcp-server-serve-usage.golden

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
🎲🎲🎲 EXIT CODE: 0 🎲🎲🎲
22
🟩🟩🟩 STDOUT️ 🟩🟩🟩️
3-
Runs the MCP server, exposing all CLI commands as MCP tools for AI assistants. Supports stdio (default), SSE, and streamable HTTP transports.
3+
Runs the MCP server, exposing all CLI commands as MCP tools for AI assistants. Supports stdio (default) and streamable HTTP transports.
44

55
USAGE:
66
scw mcp server serve [arg=value ...]
@@ -13,27 +13,24 @@ EXAMPLES:
1313
scw mcp server serve --read-only
1414

1515
Only serve commands from specific namespaces
16-
scw mcp server serve --enable-namespaces instance,iam,object
16+
scw mcp server serve namespaces=instance,iam,object
1717

1818
Only serve commands from specific resources
19-
scw mcp server serve --enable-resources server,volume,bucket
19+
scw mcp server serve resources=server,volume,bucket
2020

2121
Only serve commands with specific verbs
22-
scw mcp server serve --enable-verbs get,list,create
22+
scw mcp server serve verbs=get,list,create
2323

2424
Combine filters to serve only instance server get/list commands
25-
scw mcp server serve --enable-namespaces instance --enable-resources server --enable-verbs get,list
26-
27-
Start the MCP server with SSE transport on port 8080
28-
scw mcp server serve --transport sse --address :8080
25+
scw mcp server serve namespaces=instance resources=server verbs=get,list
2926

3027
ARGS:
31-
[transport=stdio] Transport mode: stdio (default), sse, or streamable-http
32-
[address=:8080] Address to bind for SSE and streamable-http transports (e.g., :8080)
33-
[read-only=false] Only register read-only commands (get, list operations)
34-
[enable-namespaces] Only serve commands from specified namespaces (comma-separated)
35-
[enable-resources] Only serve commands from specified resources (comma-separated)
36-
[enable-verbs] Only serve commands with specified verbs (comma-separated)
28+
[transport=stdio] Transport mode: stdio (default) or streamable-http
29+
[address=:8080] Address to bind for streamable-http transports (e.g., :8080)
30+
[read-only=false] Only register read-only commands (get, list operations)
31+
[namespaces] Only serve commands from specified namespaces (comma-separated)
32+
[resources] Only serve commands from specified resources (comma-separated)
33+
[verbs] Only serve commands with specified verbs (comma-separated)
3734

3835
FLAGS:
3936
-h, --help help for serve

core/checks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func CreateAndCloseFile(path string) error {
5151
// runAfterCommandChecks execute checks after a command has been executed
5252
// skipped if command has disabled checks or was executed in the last 24 hours
5353
func runAfterCommandChecks(ctx context.Context, checkFuncs ...AfterCommandCheckFunc) {
54-
cmd := extractMeta(ctx).command
54+
cmd := ExtractMeta(ctx).command
5555
cmdDisableCheck := cmd != nil && cmd.DisableAfterChecks
5656
if cmdDisableCheck {
5757
ExtractLogger(ctx).Debug("skipping after command checks")

core/cobra_utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func cobraRun(ctx context.Context, cmd *Command) func(*cobra.Command, []string)
2020

2121
rawArgs := args.RawArgs(rawArgsStr)
2222

23-
meta := extractMeta(ctx)
23+
meta := ExtractMeta(ctx)
2424
meta.command = cmd
2525

2626
// Check if --list-sub-commands flag is set

core/context.go

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package core
22

33
import (
44
"context"
5+
"errors"
56
"io"
67
"net/http"
78
"os"
@@ -50,71 +51,121 @@ func InjectMeta(ctx context.Context, meta *Meta) context.Context {
5051
return context.WithValue(ctx, metaContextKey, meta)
5152
}
5253

53-
// extractMeta extracts Meta from a given context.
54-
func extractMeta(ctx context.Context) *Meta {
55-
return ctx.Value(metaContextKey).(*Meta)
54+
// ExtractMeta extracts Meta from a given context.
55+
func ExtractMeta(ctx context.Context) *Meta {
56+
if meta, ok := ctx.Value(metaContextKey).(*Meta); ok {
57+
return meta
58+
}
59+
60+
return nil
5661
}
5762

5863
// injectSDKConfig add config to a Meta context
5964
func InjectConfig(ctx context.Context, config *scw.Config) {
60-
extractMeta(ctx).Platform.SetScwConfig(config)
65+
meta := ExtractMeta(ctx)
66+
if meta != nil && meta.Platform != nil {
67+
meta.Platform.SetScwConfig(config)
68+
}
6169
}
6270

6371
func extractConfig(ctx context.Context) *scw.Config {
64-
m := extractMeta(ctx)
65-
if m.Platform != nil {
72+
m := ExtractMeta(ctx)
73+
if m != nil && m.Platform != nil {
6674
return m.Platform.ScwConfig()
6775
}
6876

6977
return nil
7078
}
7179

7280
func ExtractCommands(ctx context.Context) *Commands {
73-
return extractMeta(ctx).Commands
81+
meta := ExtractMeta(ctx)
82+
if meta == nil {
83+
return nil
84+
}
85+
86+
return meta.Commands
7487
}
7588

7689
func ExtractCliConfig(ctx context.Context) *cliConfig.Config {
77-
return extractMeta(ctx).CliConfig
90+
meta := ExtractMeta(ctx)
91+
if meta == nil {
92+
return nil
93+
}
94+
95+
return meta.CliConfig
7896
}
7997

8098
func ExtractAliases(ctx context.Context) *alias.Config {
81-
return ExtractCliConfig(ctx).Alias
99+
cliConfig := ExtractCliConfig(ctx)
100+
if cliConfig == nil {
101+
return nil
102+
}
103+
104+
return cliConfig.Alias
82105
}
83106

84107
func GetOrganizationIDFromContext(ctx context.Context) string {
85108
client := ExtractClient(ctx)
109+
if client == nil {
110+
return ""
111+
}
86112
organizationID, _ := client.GetDefaultOrganizationID()
87113

88114
return organizationID
89115
}
90116

91117
func GetProjectIDFromContext(ctx context.Context) string {
92118
client := ExtractClient(ctx)
119+
if client == nil {
120+
return ""
121+
}
93122
projectID, _ := client.GetDefaultProjectID()
94123

95124
return projectID
96125
}
97126

98127
func ExtractClient(ctx context.Context) *scw.Client {
99-
return extractMeta(ctx).Client
128+
meta := ExtractMeta(ctx)
129+
if meta == nil {
130+
return nil
131+
}
132+
133+
return meta.Client
100134
}
101135

102136
func ExtractLogger(ctx context.Context) *Logger {
103-
return extractMeta(ctx).Logger
137+
meta := ExtractMeta(ctx)
138+
if meta == nil {
139+
return nil
140+
}
141+
142+
return meta.Logger
104143
}
105144

106145
func ExtractBuildInfo(ctx context.Context) *BuildInfo {
107-
return extractMeta(ctx).BuildInfo
146+
meta := ExtractMeta(ctx)
147+
if meta == nil {
148+
return nil
149+
}
150+
151+
return meta.BuildInfo
108152
}
109153

110154
func ExtractBetaMode(ctx context.Context) bool {
111-
return extractMeta(ctx).BetaMode
155+
meta := ExtractMeta(ctx)
156+
if meta == nil {
157+
return false
158+
}
159+
160+
return meta.BetaMode
112161
}
113162

114163
func ExtractEnv(ctx context.Context, envKey string) string {
115-
meta := extractMeta(ctx)
116-
if value, exist := meta.OverrideEnv[envKey]; exist {
117-
return value
164+
meta := ExtractMeta(ctx)
165+
if meta != nil {
166+
if value, exist := meta.OverrideEnv[envKey]; exist {
167+
return value
168+
}
118169
}
119170

120171
if envKey == "HOME" {
@@ -140,17 +191,28 @@ func ExtractCacheDir(ctx context.Context) string {
140191
}
141192

142193
func ExtractBinaryName(ctx context.Context) string {
143-
return extractMeta(ctx).BinaryName
194+
meta := ExtractMeta(ctx)
195+
if meta == nil {
196+
return ""
197+
}
198+
199+
return meta.BinaryName
144200
}
145201

146202
func ExtractStdin(ctx context.Context) io.Reader {
147-
return extractMeta(ctx).stdin
203+
meta := ExtractMeta(ctx)
204+
if meta == nil {
205+
return nil
206+
}
207+
208+
return meta.stdin
148209
}
149210

150211
func ExtractProfileName(ctx context.Context) string {
212+
meta := ExtractMeta(ctx)
151213
// Handle profile flag -p
152-
if extractMeta(ctx).ProfileFlag != "" {
153-
return extractMeta(ctx).ProfileFlag
214+
if meta != nil && meta.ProfileFlag != "" {
215+
return meta.ProfileFlag
154216
}
155217

156218
// Handle SCW_PROFILE env variable
@@ -170,13 +232,21 @@ func ExtractProfileName(ctx context.Context) string {
170232
}
171233

172234
func ExtractHTTPClient(ctx context.Context) *http.Client {
173-
return extractMeta(ctx).httpClient
235+
meta := ExtractMeta(ctx)
236+
if meta == nil {
237+
return nil
238+
}
239+
240+
return meta.httpClient
174241
}
175242

176243
func ExtractConfigPath(ctx context.Context) string {
177-
meta := extractMeta(ctx)
244+
meta := ExtractMeta(ctx)
245+
if meta == nil {
246+
return scw.GetConfigPath()
247+
}
178248
if meta.ConfigPathFlag != "" {
179-
return extractMeta(ctx).ConfigPathFlag
249+
return meta.ConfigPathFlag
180250
}
181251
// This is only useful for test when we override home environment variable
182252
if home := meta.OverrideEnv["HOME"]; home != "" {
@@ -187,7 +257,12 @@ func ExtractConfigPath(ctx context.Context) string {
187257
}
188258

189259
func ExtractCliConfigPath(ctx context.Context) string {
190-
meta := extractMeta(ctx)
260+
meta := ExtractMeta(ctx)
261+
if meta == nil {
262+
configPath, _ := cliConfig.FilePath()
263+
264+
return configPath
265+
}
191266
// This is only useful for test when we override home environment variable
192267
if home := meta.OverrideEnv["HOME"]; home != "" {
193268
return path.Join(home, ".config", "scw", cliConfig.DefaultConfigFileName)
@@ -198,8 +273,11 @@ func ExtractCliConfigPath(ctx context.Context) string {
198273
}
199274

200275
func ReloadClient(ctx context.Context) error {
276+
meta := ExtractMeta(ctx)
277+
if meta == nil {
278+
return errors.New("cannot reload client: meta not found in context")
279+
}
201280
var err error
202-
meta := extractMeta(ctx)
203281
meta.Client, err = meta.Platform.CreateClient(
204282
meta.httpClient,
205283
ExtractConfigPath(ctx),
@@ -210,11 +288,21 @@ func ReloadClient(ctx context.Context) error {
210288
}
211289

212290
func ExtractConfigPathFlag(ctx context.Context) string {
213-
return extractMeta(ctx).ConfigPathFlag
291+
meta := ExtractMeta(ctx)
292+
if meta == nil {
293+
return ""
294+
}
295+
296+
return meta.ConfigPathFlag
214297
}
215298

216299
func ExtractProfileFlag(ctx context.Context) string {
217-
return extractMeta(ctx).ProfileFlag
300+
meta := ExtractMeta(ctx)
301+
if meta == nil {
302+
return ""
303+
}
304+
305+
return meta.ProfileFlag
218306
}
219307

220308
// GetDocGenContext returns a minimal context that can be used by scw-doc-gen

core/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func defaultOverrideExec(cmd *exec.Cmd) (exitCode int, err error) {
1818
}
1919

2020
func ExecCmd(ctx context.Context, cmd *exec.Cmd) (exitCode int, err error) {
21-
meta := extractMeta(ctx)
21+
meta := ExtractMeta(ctx)
2222

2323
// We do not support override of stdin
2424
if cmd.Stdin == nil {

core/shell.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Completer struct {
2828
func NewShellCompleter(ctx context.Context) *Completer {
2929
return &Completer{
3030
commands: ExtractCommands(ctx),
31-
meta: extractMeta(ctx),
31+
meta: ExtractMeta(ctx),
3232
}
3333
}
3434

core/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func validateDeprecated(
188188

189189
for i := range fieldValues {
190190
if rawArgs.ExistsArgByName(strings.Replace(arg.Name, "{index}", strconv.Itoa(i), 1)) {
191-
helpCmd := cmd.GetCommandLine(extractMeta(ctx).BinaryName) + " --help"
191+
helpCmd := cmd.GetCommandLine(ExtractMeta(ctx).BinaryName) + " --help"
192192
ExtractLogger(
193193
ctx,
194194
).Warningf("The argument '%s' is deprecated, more info with: %s\n", arg.Name, helpCmd)

0 commit comments

Comments
 (0)