← back to Cli Printing Press
fix(cli): drop MCP code-orch handler pre-marshal that base64-encoded mutating bodies (#1521)
ff562375ba7aaf413707b14d6debb0dd7bd11388 · 2026-05-16 11:45:53 -0700 · Trevin Chow
Sibling fix to #1357. The code-orchestration MCP handler template
(mcp_code_orch.go.tmpl) had the same defect the endpoint-mirror template
already fixed: POST/PUT/PATCH branches json.Marshaled `params` into a
[]byte, then handed the []byte to c.Post/Put/Patch as the body. The
generated client.do() then json.Marshaled the []byte again, which
encoding/json base64-encodes into a quoted string. Strict APIs reject
the payload with errors like 'Request data must be a JSON object, not a
string'.
Pass `params` (map[string]any) directly to c.Post/Put/Patch and let
client.do() do the single marshal pass. The intermediate marshal-error
branch is removed; client.do() already wraps any marshal error with the
same "marshaling body" prefix (client.go.tmpl:878).
Add TestMCPCodeOrchPassesParamsMap mirroring TestMCPHandlerPassesBodyArgsMap
to pin the orchestrator emission shape: no json.Marshal(params)
intermediate, and the literal forms c.Post(path, params),
c.Put(path, params), c.Patch(path, params).
Golden fixture mcp-cloudflare/internal/mcp/code_orch.go updated in
lockstep per AGENTS.md "Generator Output Stability".
Closes #1426.
Files touched
M internal/generator/generator_test.goM internal/generator/templates/mcp_code_orch.go.tmplM testdata/golden/expected/generate-mcp-api/mcp-cloudflare/internal/mcp/code_orch.go
Diff
commit ff562375ba7aaf413707b14d6debb0dd7bd11388
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sat May 16 11:45:53 2026 -0700
fix(cli): drop MCP code-orch handler pre-marshal that base64-encoded mutating bodies (#1521)
Sibling fix to #1357. The code-orchestration MCP handler template
(mcp_code_orch.go.tmpl) had the same defect the endpoint-mirror template
already fixed: POST/PUT/PATCH branches json.Marshaled `params` into a
[]byte, then handed the []byte to c.Post/Put/Patch as the body. The
generated client.do() then json.Marshaled the []byte again, which
encoding/json base64-encodes into a quoted string. Strict APIs reject
the payload with errors like 'Request data must be a JSON object, not a
string'.
Pass `params` (map[string]any) directly to c.Post/Put/Patch and let
client.do() do the single marshal pass. The intermediate marshal-error
branch is removed; client.do() already wraps any marshal error with the
same "marshaling body" prefix (client.go.tmpl:878).
Add TestMCPCodeOrchPassesParamsMap mirroring TestMCPHandlerPassesBodyArgsMap
to pin the orchestrator emission shape: no json.Marshal(params)
intermediate, and the literal forms c.Post(path, params),
c.Put(path, params), c.Patch(path, params).
Golden fixture mcp-cloudflare/internal/mcp/code_orch.go updated in
lockstep per AGENTS.md "Generator Output Stability".
Closes #1426.
---
internal/generator/generator_test.go | 63 ++++++++++++++++++++++
internal/generator/templates/mcp_code_orch.go.tmpl | 21 +++-----
.../mcp-cloudflare/internal/mcp/code_orch.go | 21 +++-----
3 files changed, 77 insertions(+), 28 deletions(-)
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index e7335a11..7750aff1 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -10056,6 +10056,69 @@ func TestMCPHandlerPassesBodyArgsMap(t *testing.T) {
"PATCH branch must forward bodyArgs directly to the client")
}
+// TestMCPCodeOrchPassesParamsMap pins the same body-passthrough contract for
+// the code-orchestration handler template (mcp_code_orch.go.tmpl). Sibling of
+// TestMCPHandlerPassesBodyArgsMap: an earlier pre-marshal stored []byte in the
+// body slot, which client.do() then json.Marshaled into a base64-encoded
+// string that strict APIs reject.
+func TestMCPCodeOrchPassesParamsMap(t *testing.T) {
+ t.Parallel()
+
+ apiSpec := minimalSpec("mcp-orch-body-passthrough")
+ apiSpec.MCP = spec.MCPConfig{Orchestration: "code", EndpointTools: "hidden"}
+ apiSpec.Resources["widgets"] = spec.Resource{
+ Description: "Widgets",
+ Endpoints: map[string]spec.Endpoint{
+ "create": {
+ Method: "POST",
+ Path: "/widgets",
+ Description: "Create a widget",
+ Body: []spec.Param{
+ {Name: "label", Type: "string", Required: true, Description: "Widget label"},
+ },
+ },
+ "replace": {
+ Method: "PUT",
+ Path: "/widgets/{id}",
+ Description: "Replace a widget",
+ Params: []spec.Param{
+ {Name: "id", Type: "string", Required: true, Description: "Widget id"},
+ },
+ Body: []spec.Param{
+ {Name: "label", Type: "string", Required: true, Description: "Widget label"},
+ },
+ },
+ "update": {
+ Method: "PATCH",
+ Path: "/widgets/{id}",
+ Description: "Update a widget",
+ Params: []spec.Param{
+ {Name: "id", Type: "string", Required: true, Description: "Widget id"},
+ },
+ Body: []spec.Param{
+ {Name: "label", Type: "string", Required: true, Description: "Widget label"},
+ },
+ },
+ },
+ }
+
+ outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
+ require.NoError(t, New(apiSpec, outputDir).Generate())
+
+ mcpSource := readGeneratedFile(t, outputDir, "internal", "mcp", "code_orch.go")
+
+ assert.NotContains(t, mcpSource, "json.Marshal(params)",
+ "code-orch handler must not pre-marshal params: client.do() json.Marshals what it receives, "+
+ "so a []byte arrives base64-encoded on the wire and strict APIs reject the payload")
+
+ assert.Contains(t, mcpSource, "c.Post(path, params)",
+ "POST branch must forward params directly to the client")
+ assert.Contains(t, mcpSource, "c.Put(path, params)",
+ "PUT branch must forward params directly to the client")
+ assert.Contains(t, mcpSource, "c.Patch(path, params)",
+ "PATCH branch must forward params directly to the client")
+}
+
// TestMCPBindingNumericTypesAcrossSpecShapes pins template wiring: both
// OpenAPI-parsed shapes ("int", "bool") and internal-spec literals
// ("integer", "boolean") flow through mcpBindingFunc to WithNumber /
diff --git a/internal/generator/templates/mcp_code_orch.go.tmpl b/internal/generator/templates/mcp_code_orch.go.tmpl
index d334784b..d6444ba1 100644
--- a/internal/generator/templates/mcp_code_orch.go.tmpl
+++ b/internal/generator/templates/mcp_code_orch.go.tmpl
@@ -249,21 +249,14 @@ func handleCodeOrchExecute(ctx context.Context, req mcplib.CallToolRequest) (*mc
data, err = c.Get(path, query)
case "DELETE":
data, _, err = c.DeleteWithParams(path, query)
+ case "POST":
+ data, _, err = c.Post(path, params)
+ case "PUT":
+ data, _, err = c.Put(path, params)
+ case "PATCH":
+ data, _, err = c.Patch(path, params)
default:
- body, mErr := json.Marshal(params)
- if mErr != nil {
- return mcplib.NewToolResultError(fmt.Sprintf("marshaling body: %v", mErr)), nil
- }
- switch ep.Method {
- case "POST":
- data, _, err = c.Post(path, body)
- case "PUT":
- data, _, err = c.Put(path, body)
- case "PATCH":
- data, _, err = c.Patch(path, body)
- default:
- return mcplib.NewToolResultError(fmt.Sprintf("unsupported method %q", ep.Method)), nil
- }
+ return mcplib.NewToolResultError(fmt.Sprintf("unsupported method %q", ep.Method)), nil
}
if err != nil {
return mcplib.NewToolResultError(err.Error()), nil
diff --git a/testdata/golden/expected/generate-mcp-api/mcp-cloudflare/internal/mcp/code_orch.go b/testdata/golden/expected/generate-mcp-api/mcp-cloudflare/internal/mcp/code_orch.go
index a67ac1ef..07aa5132 100644
--- a/testdata/golden/expected/generate-mcp-api/mcp-cloudflare/internal/mcp/code_orch.go
+++ b/testdata/golden/expected/generate-mcp-api/mcp-cloudflare/internal/mcp/code_orch.go
@@ -219,21 +219,14 @@ func handleCodeOrchExecute(ctx context.Context, req mcplib.CallToolRequest) (*mc
data, err = c.Get(path, query)
case "DELETE":
data, _, err = c.DeleteWithParams(path, query)
+ case "POST":
+ data, _, err = c.Post(path, params)
+ case "PUT":
+ data, _, err = c.Put(path, params)
+ case "PATCH":
+ data, _, err = c.Patch(path, params)
default:
- body, mErr := json.Marshal(params)
- if mErr != nil {
- return mcplib.NewToolResultError(fmt.Sprintf("marshaling body: %v", mErr)), nil
- }
- switch ep.Method {
- case "POST":
- data, _, err = c.Post(path, body)
- case "PUT":
- data, _, err = c.Put(path, body)
- case "PATCH":
- data, _, err = c.Patch(path, body)
- default:
- return mcplib.NewToolResultError(fmt.Sprintf("unsupported method %q", ep.Method)), nil
- }
+ return mcplib.NewToolResultError(fmt.Sprintf("unsupported method %q", ep.Method)), nil
}
if err != nil {
return mcplib.NewToolResultError(err.Error()), nil
← 95417401 fix(cli): filter auth login --chrome cookies by spec auth.co
·
back to Cli Printing Press
·
fix(cli): detect integer page paginator and advance numerica 0c3c8bcc →