Skip to content

feat(router): add MCP Code Mode with search and execute tools#2575

Draft
jensneuse wants to merge 20 commits intomainfrom
jensneuse/mcp-uber-endpoint-rfc
Draft

feat(router): add MCP Code Mode with search and execute tools#2575
jensneuse wants to merge 20 commits intomainfrom
jensneuse/mcp-uber-endpoint-rfc

Conversation

@jensneuse
Copy link
Copy Markdown
Member

@jensneuse jensneuse commented Mar 2, 2026

Summary

  • Adds MCP Code Mode, a new MCP server mode for the router that exposes two uber-tools (search + execute) accepting TypeScript code, executed in a secure JS sandbox
  • Enables AI agents to dynamically discover and execute GraphQL operations against the supergraph via natural language (using Yoko query generation) without pre-defined .graphql operation files
  • Implements secure JS sandbox infrastructure with two runtimes: QuickJS via WASM (default, memory-isolated) and goja (pure Go fallback), with resource limits (timeout, memory, input/output size)

Key components

  • Sandbox (router/pkg/sandbox/): Runtime interface, QuickJS/WASM and goja implementations, esbuild transpiler, semaphore-based pool
  • Code Mode server (router/pkg/mcpserver/code_mode_server.go): search tool (generateQuery via Yoko) and execute tool (graphql via router endpoint)
  • Yoko client (router/pkg/yokoclient/): HTTP client for prompt-to-query generation REST API with static token and JWT auth
  • Features: Query hash deduplication, TOON encoding for token efficiency, mutation approval via MCP elicitation, OTel metrics/tracing, CORS, retryable HTTP client
  • Demo: Mock Yoko server (router/cmd/yoko-mock/), demo script, and config

See rfc/mcp-agent-mode.md for the full design document.

Test plan

  • Run router/pkg/sandbox/ unit tests (runtime, transpiler, pool)
  • Run router/pkg/yokoclient/ unit tests (client, mock)
  • Run router/pkg/mcpserver/ unit tests (code mode server)
  • Run the demo script (make code-mode-demo) and verify end-to-end with an MCP client
  • Verify existing MCP per-operation mode is unaffected
  • Verify config defaults and full config test data pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Code Mode MCP server for agent-driven NL→GraphQL generation and safe execution.
    • Sandboxed TypeScript/JavaScript runtime with configurable resource limits and mutation approval flow.
    • Query generation integration (Yoko) and in-memory query hashing for reuse.
    • New demo tooling and a Makefile target to run a complete Code Mode demo.
  • Tests

    • Extensive test suites added for Code Mode, sandbox runtimes, transpilation, and Yoko client.

Adds a new "Code Mode" MCP server that exposes two uber-tools (search + execute)
accepting TypeScript code as input, executed in a secure JS sandbox. This enables
AI agents to dynamically discover and execute GraphQL operations against the
supergraph without pre-defined operation files.

Key components:
- Secure JS sandbox (router/pkg/sandbox/) with two runtimes: QuickJS via WASM
  (default) and goja (pure Go fallback), with resource limits and isolation
- Code Mode MCP server (router/pkg/mcpserver/code_mode_server.go) with search
  tool (generateQuery via Yoko REST API) and execute tool (graphql via router)
- Yoko client (router/pkg/yokoclient/) for prompt-to-query generation with
  static token and JWT auth support
- Query hash deduplication, TOON encoding, mutation approval via MCP elicitation
- OpenTelemetry metrics and tracing, CORS support, retryable HTTP client
- Demo infrastructure: yoko-mock server, demo script, and config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an MCP "Code Mode" subsystem: a sandboxed TypeScript→JS execution environment, Yoko query-generation client and mock, MCP search/execute tools, router wiring, configs, demo scripts, and extensive tests and docs. (≈45 words)

Changes

Cohort / File(s) Summary
Build & Demo tooling
Makefile, router/.gitignore, router/scripts/code-mode-demo.sh, router/scripts/code-mode-graph.yaml, router/code-mode-demo.config.yaml
New make target and demo orchestration script; gitignore additions; demo graph YAML and Code Mode config. Check script env and port defaults.
Router core & bootstrap
router/core/router.go, router/core/router_config.go
Wires Code Mode server into router bootstrap/shutdown, adds CORS propagation and changed bootstrap signature; verify startup error-handling and deferred cleanup.
Configuration types & schemas
router/pkg/config/config.go, router/pkg/config/config.schema.json, router/pkg/config/testdata/config_defaults.json, router/pkg/config/testdata/config_full.json
Adds CodeModeConfiguration and nested Server/Sandbox/QueryGeneration types and JSON/YAML schema defaults. Review YAML/env tags and default values.
MCP Code Mode server implementation
router/pkg/mcpserver/code_mode_server.go, router/pkg/mcpserver/code_mode_descriptions.go, router/pkg/mcpserver/server.go, router/pkg/mcpserver/util.go
Large new CodeModeServer with search/execute tools, type defs, query hashing, mutation-approval flow, Yoko integration, metrics/tracing, and HTTP lifecycle tweaks; also removed local Logger interface. Focus review on security boundaries, elicitation flow, header propagation, and shutdown semantics.
Sandbox runtime & pool
router/pkg/sandbox/runtime.go, router/pkg/sandbox/runtime_qjs.go, router/pkg/sandbox/pool.go, router/pkg/sandbox/transpiler.go
Introduces Runtime API, QuickJS-based runtime implementation (async bridging, globals lockdown), bounded Pool, and TypeScript transpiler via esbuild. Pay attention to concurrency, resource limits, argument marshalling, and error/recovery paths.
Sandbox tests
router/pkg/sandbox/*_test.go (runtime_test.go, runtime_qjs_test.go, pool_test.go, transpiler_test.go)
Extensive tests for runtime, QJS interop, pool concurrency, transpilation, and resource limits. Useful to validate behavior during review.
MCP Code Mode tests
router/pkg/mcpserver/code_mode_server_test.go
Large test suite covering search/execute handlers, GraphQL interactions, mutation approval flows, query hash store, and Yoko integration. Review for coverage and brittle assumptions.
Yoko client & mock
router/pkg/yokoclient/client.go, router/pkg/yokoclient/mock.go, router/pkg/yokoclient/mock_openapi.yaml, router/pkg/yokoclient/*_test.go
Adds HTTP client with static/JWT auth and token caching, plus a schema-driven MockClient and OpenAPI spec. Inspect auth/token caching and error handling.
Demo code change
demo/pkg/subgraphs/employees/subgraph/schema.resolvers.go
UpdateEmployeeTag now mutates the existing employee in-place (sets Tag and UpdatedAt) instead of returning a copied struct. Confirm semantics align with consumers.
Deps & go.mod updates
router/go.mod, router-tests/go.mod
Adds esbuild, qjs, toon, updates mcp-go and other indirect deps. Verify module graph and CI build implications.
RFC & documentation
rfc/mcp-agent-mode.md
Large new RFC describing architecture, tools, sandbox model, security, and phased rollout. Use for design/context during review.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.34% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(router): add MCP Code Mode with search and execute tools' is fully related to the main changes in the pull request. It accurately summarizes the primary feature addition: a new MCP mode for the router that introduces two tools (search and execute) for Code Mode functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 2, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-bb98636db5b19565ae4f8e86948110c8869f2ce1

@jensneuse jensneuse marked this pull request as draft March 2, 2026 16:02
jensneuse and others added 2 commits March 2, 2026 17:02
The MCP Code Mode feature added new dependencies (mcp-go v0.43.2, esbuild,
goja, qjs, toon-go) that need to be reflected in router-tests go.sum.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (6)
router/pkg/yokoclient/mock_openapi.yaml-69-72 (1)

69-72: ⚠️ Potential issue | 🟡 Minor

Bound the queries array in the response schema.

Line 69 defines an unbounded array. Add maxItems to keep the contract aligned with response-size controls.

Proposed schema bound
         queries:
           type: array
+          maxItems: 20
           items:
             $ref: '#/components/schemas/QueryResult'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/mock_openapi.yaml` around lines 69 - 72, The schema for
the response array "queries" (items: $ref: '#/components/schemas/QueryResult')
is currently unbounded; update that array schema in
router/pkg/yokoclient/mock_openapi.yaml to include a sensible maxItems value
(e.g., maxItems: 100) so the contract enforces a response-size cap and aligns
with response-size controls; keep the same items reference and, if desired, also
add minItems or descriptive comment to document the chosen bound.
rfc/mcp-agent-mode.md-40-67 (1)

40-67: ⚠️ Potential issue | 🟡 Minor

Add language tags to fenced code blocks.

Line 40 and Line 124 use unlabeled fences, which triggers markdownlint MD040.

💡 Suggested doc fix
-```
+```http
 POST {endpoint}/v1/generate
 Authorization: Bearer {token}
 Content-Type: application/json
@@
-```
+```

-```
+```text
 ┌─────────────────────────────────────────────────────────┐
 │                    AI Agent (Claude, etc.)               │
@@
-```
+```

Also applies to: 124-155

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rfc/mcp-agent-mode.md` around lines 40 - 67, The markdown contains unlabeled
fenced code blocks (the HTTP POST request/response sample and the ASCII AI Agent
box) which triggers MD040; update the fences to include language tags (e.g.,
change the POST request/response block to ```http and the ASCII agent/diagram
block to a suitable tag like ```text or ```ascii) and apply the same labeling to
the other unlabeled block later in the file so all unlabeled fenced code blocks
are explicitly tagged.
rfc/mcp-agent-mode.md-783-785 (1)

783-785: ⚠️ Potential issue | 🟡 Minor

Implementation summary references outdated filenames.

Line 783 mentions router/pkg/mcpserver/agentic_server.go / agentic_descriptions.go, but this PR is centered on code_mode_* naming. This can mislead readers navigating the code.

💡 Suggested doc fix
-- Created `router/pkg/mcpserver/agentic_server.go` and `agentic_descriptions.go`.
+- Created `router/pkg/mcpserver/code_mode_server.go` and `code_mode_descriptions.go`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rfc/mcp-agent-mode.md` around lines 783 - 785, The doc references outdated
filenames and resource names (router/pkg/mcpserver/agentic_server.go,
agentic_descriptions.go, and agentic://search-api.d.ts) which should be updated
to the PR's current naming (e.g., code_mode_server.go, code_mode_descriptions.go
and code_mode://search-api.d.ts); edit rfc/mcp-agent-mode.md to replace those
legacy strings, ensure mentions of removed APIs (schema.* and validate()) and
implemented functions (generateQuery()) remain accurate, and scan the
surrounding text for any other "agentic_" identifiers to rename to the
code_mode_* equivalents so readers are not misdirected.
router/pkg/sandbox/runtime_goja.go-120-121 (1)

120-121: ⚠️ Potential issue | 🟡 Minor

Harden Math.random and Date.now to prevent runtime reassignment.

The current implementation uses simple reassignment, which sandbox code can override. Use Object.defineProperty with immutable descriptors to prevent further modification:

🔒 Proposed hardening
-		_, _ = vm.RunString("Math.random = function() { return 0; }; Date.now = function() { return 0; };")
+		_, _ = vm.RunString(`
+Object.defineProperty(Math, "random", { value: function() { return 0; }, writable: false, configurable: false });
+Object.defineProperty(Date, "now", { value: function() { return 0; }, writable: false, configurable: false });
+`)

This pattern is already used in runtime_qjs.go for similar sandbox hardening and aligns with defense-in-depth security principles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/runtime_goja.go` around lines 120 - 121, Replace the
simple reassignment call to vm.RunString that sets Math.random and Date.now with
a hardened definition using Object.defineProperty (called from the same
vm.RunString invocation in runtime_goja.go) so both Math.random and Date.now are
defined with non-writable and non-configurable descriptors that return 0; update
the code around the vm.RunString usage to define the functions via
Object.defineProperty on Math and Date (and ensure Math exists) so sandboxed
scripts cannot later reassign or configure those properties.
router/pkg/yokoclient/mock.go-165-176 (1)

165-176: ⚠️ Potential issue | 🟡 Minor

Account for argument default values when determining if a field has required arguments.

Per the GraphQL specification, arguments with default values are optional regardless of their type. The current logic only checks for non-null types, causing fields with non-null arguments that have defaults to be unnecessarily skipped during mock query generation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/mock.go` around lines 165 - 176, hasRequiredArgs
currently treats any NonNull argument as required; update it to consider
argument default values so arguments with defaults are treated as optional. In
the hasRequiredArgs function, for each argRef call
m.schema.InputValueDefinitionDefaultValue(argRef) (or the project-specific
accessor for the default) and only treat a NonNull type as required when that
default is absent; i.e. return true when t.TypeKind == ast.TypeKindNonNull AND
the argument has no default value. Also ensure the function returns false at the
end if no required args are found. Use the existing symbols: hasRequiredArgs,
m.schema.InputValueDefinitionType, m.schema.Types, and
m.schema.InputValueDefinitionDefaultValue (or equivalent) to locate and
implement the change.
router/pkg/mcpserver/code_mode_server_test.go-431-433 (1)

431-433: ⚠️ Potential issue | 🟡 Minor

Consider handling decode errors in mock server.

The JSON decode error is silently ignored. While this is test code, unhandled errors could mask issues during test development.

Proposed fix
 		var body map[string]any
-		json.NewDecoder(r.Body).Decode(&body)
+		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+			t.Errorf("failed to decode request body: %v", err)
+			http.Error(w, "bad request", http.StatusBadRequest)
+			return
+		}
 		query := body["query"].(string)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server_test.go` around lines 431 - 433,
Capture and check the error returned by json.NewDecoder(r.Body).Decode(&body)
instead of ignoring it; if decoding fails return early (e.g., call http.Error(w,
"invalid JSON: "+err.Error(), http.StatusBadRequest)) or fail the test
(t.Fatalf("...: %v", err)) so the mock handler does not continue to access
body["query"] when Decode failed; update the lines around
json.NewDecoder(r.Body).Decode(&body) and the subsequent use of query to perform
this check.
🧹 Nitpick comments (9)
router/scripts/code-mode-demo.sh (1)

57-65: Use readiness probes instead of fixed sleeps.

Line 57 and Line 65 depend on timing assumptions. This makes the demo flaky on slower machines/CI runners.

💡 Suggested refactor
+wait_for_http() {
+    local url="$1"
+    local name="$2"
+    local retries="${3:-60}"
+    local delay="${4:-1}"
+    for _ in $(seq 1 "$retries"); do
+        if curl -fsS "$url" >/dev/null 2>&1; then
+            echo "  $name is ready"
+            return 0
+        fi
+        sleep "$delay"
+    done
+    echo "  $name did not become ready in time"
+    return 1
+}
@@
 go run cmd/all/main.go &
 SUBGRAPH_PID=$!
 echo "  Subgraphs starting (PID: $SUBGRAPH_PID)"
-sleep 3  # Give subgraphs time to start
+wait_for_http "http://localhost:4001/graphql" "Subgraphs"
@@
 go run cmd/yoko-mock/main.go --addr localhost:5030 --schema "$SCHEMA_SDL" &
 YOKO_PID=$!
 echo "  Yoko mock server starting (PID: $YOKO_PID)"
-sleep 1
+wait_for_http "http://localhost:5030/health" "Yoko mock"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/scripts/code-mode-demo.sh` around lines 57 - 65, Replace the fixed
sleep calls with readiness probes that poll the started services until they are
actually ready: after starting subgraphs and before starting the Yoko mock, loop
and check their listening ports or health endpoints (e.g., attempt TCP connect
or curl the health URL) instead of sleep 3; after launching the Yoko mock via
the go run command shown (go run cmd/yoko-mock/main.go --addr localhost:5030
--schema "$SCHEMA_SDL" & and YOKO_PID=$!), replace sleep 1 with a loop that
probes localhost:5030 (TCP connect or HTTP health check) until it responds or a
timeout is reached, and fail with a clear error if the probe times out; keep
using ROUTER_DIR, SCHEMA_SDL and YOKO_PID names so the change is localized and
easy to review.
router/pkg/yokoclient/mock.go (1)

185-209: Consider caching scalar/type lookups in this mock client.

isScalar and getTypeFields repeatedly scan RootNodes; precomputed maps would reduce repeated O(n) traversals and simplify code paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/mock.go` around lines 185 - 209, Add cached lookup maps
to MockClient (e.g., scalarSet map[string]bool and typeFields map[string][]int)
and populate them once when the schema is loaded/assigned (populate by iterating
m.schema.RootNodes and using m.schema.ScalarTypeDefinitionNameString and
m.schema.ObjectTypeDefinitionNameString to fill the maps); then change isScalar
to check scalarSet first (with an optional fallback to the scan if map is nil)
and change getTypeFields to return typeFields[typeName] (with a nil/empty
fallback), ensuring the cache keys use the exact strings returned by
ScalarTypeDefinitionNameString/ObjectTypeDefinitionNameString to match existing
behavior.
router/pkg/mcpserver/code_mode_descriptions.go (1)

10-53: Reduce duplicated TS contracts to avoid drift.

The same API/type contract is duplicated across inline tool descriptions and MCP resource typedefs. Consider generating both from shared constants/templates.

Also applies to: 59-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_descriptions.go` around lines 10 - 53, The
inline type/contracts in searchToolDescription and executeToolDescription are
duplicated elsewhere; extract the shared TypeScript contracts (e.g., the
QueryResult interface and GraphQLOptions/GraphQLResponse/GraphQLError
definitions) into a single canonical constant or template (e.g.,
sharedTypeContracts or graphqlContracts) and reference or interpolate that
constant into both searchToolDescription and executeToolDescription (and the MCP
resource typedefs) so both use the same source-of-truth; update code that
previously declared the interfaces inline to import/use the shared
constant/template to prevent drift.
router/pkg/sandbox/pool_test.go (1)

33-37: Use WaitGroup.Go() instead of manual Add/Done pattern.

Go 1.25 provides sync.WaitGroup.Go(func()) which automatically manages Add and Done calls for launched goroutines. Replace the manual pattern throughout:

  • Lines 33–37: wg.Add(1); go func(idx int) { defer wg.Done(); ... }(i)wg.Go(func() { ... })
  • Lines 76–80: Same pattern
  • Lines 118–126: started.Add(1); go func() { started.Done(); ... }()started.Go(func() { ... })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/pool_test.go` around lines 33 - 37, Replace manual
goroutine Add/Done patterns with sync.WaitGroup.Go: for the loops using
wg.Add(1); go func(idx int){ defer wg.Done(); ... }(i) switch to capturing the
loop variable (e.g. idx := i) and call wg.Go(func(){ ... use idx ... }) and
remove the defer wg.Done(); likewise replace started.Add/started.Done with
started.Go(func(){ ... }) and remove the explicit Done call; ensure any
loop-variable usage is copied to a local variable before calling Go to avoid
closure issues.
router/pkg/sandbox/transpiler.go (1)

69-74: Consider adding test coverage for string literal preservation when stripping async/await.

The developer has already documented why this approach is safe for goja mode—input is strictly constrained to async arrow functions wrapped as IIFEs, which are minified by esbuild before stripping. However, no test validates that string literals containing "async " or "await " are preserved. Adding a test case (e.g., async () => { const msg = "async task"; return msg; }) would strengthen confidence in this approach and document the assumption that input constraints prevent corruption.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/transpiler.go` around lines 69 - 74, Add a unit test that
validates stripAsyncAwait preserves string literals containing "async " and
"await " by calling stripAsyncAwait with an IIFE async arrow function that
includes const msg = "async task" and/or "await something" in a string and
asserting the returned string still contains those string literals; reference
the stripAsyncAwait function in transpiler.go and create a test (e.g.,
TestStripAsyncAwait_PreservesStringLiterals) that covers both raw and
minified-like inputs to document and enforce the assumption that only the
async/await keywords are removed, not content inside string literals.
router/pkg/mcpserver/code_mode_server.go (4)

607-616: Use sync.WaitGroup.Go() instead of manual Add/Done pattern.

The manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() pattern should be replaced with wg.Go(func()) as per the repository's Go 1.25+ conventions.

Proposed fix
 		var wg sync.WaitGroup
 		for i, p := range prompts {
-			wg.Add(1)
-			go func(idx int, prompt string) {
-				defer wg.Done()
+			wg.Go(func() {
 				results, err := s.yokoClient.Generate(ctx, prompt, schemaHash)
 				ch <- indexedResult{index: idx, results: results, err: err}
-			}(i, p)
+			})
 		}

Note: You'll need to capture loop variables properly for Go versions < 1.22, or ensure the loop uses the Go 1.22+ semantics where loop variables are per-iteration.

Based on learnings: "In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 607 - 616, Replace the
manual wg.Add(1) / defer wg.Done() pattern with wg.Go(...) to spawn the
goroutine for each prompt: in the loop over prompts use wg.Go(func() { ... }) to
call s.yokoClient.Generate(ctx, prompt, schemaHash) and send indexedResult on
ch; ensure loop variables are captured correctly by creating local copies (e.g.,
idx, p or prompt) before using them in the closure so s.yokoClient.Generate,
indexedResult{index: idx, results: results, err: err}, and ch usage refer to the
intended per-iteration values.

234-244: Formatting issue with extra parenthesis line.

The execute tool registration has an unusual empty line 242 with just ),. While syntactically valid, this appears to be a formatting inconsistency compared to the search tool registration above.

Proposed fix for consistency
 	// Execute tool
 	s.mcpServer.AddTool(
 		mcp.NewTool(executeToolName,
 			mcp.WithDescription(executeToolDescription),
 			mcp.WithString("code",
 				mcp.Required(),
 				mcp.Description("An async arrow function. Example: async () => { const { data } = await graphql({ query: '{ users { id } }' }); return data; }"),
 			),
-			),
+		),
 		s.handleExecute(),
 	)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 234 - 244, The tool
registration for execute (mcp.NewTool with executeToolName and
mcp.WithString("code")) has an extra stand-alone closing tuple line containing
just "),", causing a formatting inconsistency; edit the call that constructs the
execute tool (the mcp.NewTool(...) invocation used with executeToolName and
s.handleExecute()) to remove the stray closing line and collapse the parentheses
so the mcp.WithString block and the NewTool call are closed consistently
(matching the style used by the search tool registration).

667-676: Consider LRU eviction instead of clearing entire store.

The current eviction strategy clears the entire map when reaching maxQueryStoreSize. This could cause all in-flight operations to fail simultaneously if they rely on recently stored hashes.

An LRU cache would provide more graceful eviction, keeping the most recently used queries available.

Alternative using an LRU cache

Consider using github.com/hashicorp/golang-lru/v2 or similar:

+import lru "github.com/hashicorp/golang-lru/v2"
+
 type CodeModeServer struct {
 	// ...
-	queryStoreMu sync.RWMutex
-	queryStore   map[string]string
+	queryStore *lru.Cache[string, string]
 }

 // In NewCodeModeServer:
-	queryStore: make(map[string]string),
+	// queryStore initialized with LRU cache

If the current approach is intentional for simplicity, consider adding a comment explaining the tradeoff.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 667 - 676, The
storeQueryHash function currently clears the entire s.queryStore map when it
reaches maxQueryStoreSize, which can evict all entries at once; replace the
plain map with an LRU cache (e.g., github.com/hashicorp/golang-lru/v2) stored on
the CodeModeServer struct (replace s.queryStore and s.queryStoreMu with an
lru.Cache instance and its mutex if needed) and update storeQueryHash to Add the
hash→query into the LRU (use cache.Add(hash, query)) so old entries are evicted
incrementally; alternatively, if the all-clear behavior is intentional, add a
clear comment in storeQueryHash documenting the tradeoff and why simple clear
was chosen.

104-111: Consider logging metric registration failures.

The errors from meter.Int64Counter and meter.Float64Histogram are silently discarded. While metric failures shouldn't break the server, logging them would help diagnose observability issues.

Proposed fix
 	meter := otel.Meter("wundergraph.cosmo.router.mcp.code_mode")
-	execCounter, _ := meter.Int64Counter("mcp.code_mode.sandbox.executions",
+	execCounter, err := meter.Int64Counter("mcp.code_mode.sandbox.executions",
 		otelmetric.WithDescription("Total number of Code Mode sandbox executions"),
 	)
-	execDuration, _ := meter.Float64Histogram("mcp.code_mode.sandbox.duration",
+	if err != nil {
+		cfg.Logger.Warn("failed to create executions counter", zap.Error(err))
+	}
+	execDuration, err := meter.Float64Histogram("mcp.code_mode.sandbox.duration",
 		otelmetric.WithDescription("Duration of Code Mode sandbox executions in milliseconds"),
 		otelmetric.WithUnit("ms"),
 	)
+	if err != nil {
+		cfg.Logger.Warn("failed to create duration histogram", zap.Error(err))
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 104 - 111, The code
currently discards errors returned from meter.Int64Counter and
meter.Float64Histogram; update the initialization around meter, execCounter and
execDuration to check the returned error values and log any non-nil errors (use
the file's existing logger instance, e.g., logger or s.logger, or fall back to
the standard log package) so metric registration failures are recorded without
changing control flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/cmd/yoko-mock/main.go`:
- Around line 83-86: The handler currently returns internal error text
(err.Error()) to API clients via writeError (seen around the writeError(...)
calls at the top block and the similar calls at lines ~144-146), which can leak
raw generator/Claude output; change these writeError calls to return a generic
client-facing message (e.g., "internal server error" or "query generation
failed") and avoid including err.Error() in the HTTP response. Continue to log
the full error and elapsed time with log.Printf (or processLogger) for
debugging, and optionally generate an internal error ID to return to the client
so logs can be correlated without exposing raw error contents. Ensure this
sanitization is applied to all places that call writeError with err.Error()
(including the parse error path).
- Around line 67-68: The handler decodes r.Body with
json.NewDecoder(r.Body).Decode(&req) without limiting size; wrap the request
body with http.MaxBytesReader (or io.LimitReader) before decoding to enforce a
safe max (e.g., 1MB), check the decode error for io.ErrUnexpectedEOF or
http.ErrBodyReadAfterClose/“request body too large” and return a 413 or 400 via
writeError if the body exceeds the limit or JSON is invalid; update the code
around json.NewDecoder/Decode (and r.Body usage) to use the limited reader and
handle the oversized-body error path.

In `@router/go.mod`:
- Line 81: The module bump to github.com/mark3labs/mcp-go v0.43.2 introduces
breaking changes; either pin back to the known-compatible v0.36.0 in
router/go.mod or migrate router code to the new API: update all usages of
Result.Meta to accept/handle *mcp.Meta (change types and any map access
accordingly), update any TokenStore implementations and calls (in server.go and
code_mode_server.go) to add context.Context as the first parameter to the
affected methods, and fix construction of mcp.Implementation values by switching
to keyed struct literals and populating the new Title field where applicable;
search for Result.Meta, TokenStore, and mcp.Implementation usages in server.go
and code_mode_server.go to make these targeted edits.

In `@router/pkg/config/config.schema.json`:
- Around line 2348-2380: The sandbox schema under "sandbox" must include the
missing "max_input_size_bytes" property and add validation bounds for numeric
limits: update properties "max_memory_mb", "max_fuel", and
"max_output_size_bytes" (and the new "max_input_size_bytes") to include
"minimum" (e.g., 0 or 1 as appropriate) and sensible "maximum" values consistent
with config/runtime, and adjust any "default" values if needed; ensure
"additionalProperties" remains false and document the new field's "description"
and "default".
- Around line 2397-2412: The auth schema currently only allows "type" and
"static_token", so add explicit properties for the Cosmo flow (e.g.,
"token_endpoint", "client_id", "client_secret") under the "auth" object and
enforce required fields conditionally when "type" == "cosmo" using JSON Schema
conditional validation (if/then or oneOf) so YAML configs can validate
non-static auth; keep additionalProperties: false and ensure the "auth" object
(and its "type" enum) is the target for these updates (reference the "auth"
schema, its "type" property, and add the new property names).

In `@router/pkg/mcpserver/server.go`:
- Around line 420-423: The current shutdown block swallows errors from
s.rawHTTPServer.Shutdown and Close; change it to surface failures to the caller
by capturing the error from s.rawHTTPServer.Shutdown(ctx) (and from
s.rawHTTPServer.Close() if Shutdown fails) and returning that error instead of
only logging it. Locate the code using s.rawHTTPServer.Shutdown and
s.rawHTTPServer.Close (in the server's shutdown/Stop/Close method) and: 1) if
Shutdown returns non-nil, attempt Close and if Close also returns non-nil, wrap
or aggregate those errors (or return the first meaningful one); 2) keep the
s.logger.Warn call but then return the error to the caller so upstream can
observe shutdown failure; 3) update the method signature to return error if it
doesn't already and adjust callers to handle the returned error.

In `@router/pkg/sandbox/pool_test.go`:
- Around line 80-99: The test injects a SyncFunc named trackConcurrency but the
JS snippet never calls it, so maxSeen and concurrent are never updated; update
the JS passed to pool.Execute (the snippet in the pool.Execute call) to actually
invoke the injected sync function (e.g., call trackConcurrency() inside the
heavy loop or call it repeatedly from the JS IIFE) so that trackConcurrency runs
and updates concurrent and maxSeen; ensure the SyncFunc name
("trackConcurrency") matches the JS call and keep using the existing
SyncFunc/variables (SyncFunc, trackConcurrency, maxSeen, concurrent) to assert
measured concurrency.

In `@router/pkg/sandbox/runtime_goja.go`:
- Around line 37-41: In Execute (method gojaRuntime.Execute) enforce
r.config.MaxInputBytes before parsing/executing: check len(jsCode) against
r.config.MaxInputBytes (when >0) and return a clear error (and no Result) if the
input exceeds the limit so oversized scripts are rejected early; do this check
immediately after determining timeout and before any further processing,
referencing Execute and r.config.MaxInputBytes to locate the change.
- Around line 127-129: The goja.InterruptedError handling in the exec goroutine
incorrectly always labels interrupts as timeouts; update the block that checks
err.(*goja.InterruptedError) (where execResult is sent) to inspect
interrupted.Value() and ctx.Err(): if interrupted.Value() == "context cancelled"
and ctx.Err() != nil, send an execResult with an error indicating execution
cancelled (include ctx.Err() or interrupted.Value()), otherwise keep the
existing timeout-style fmt.Errorf using timeout and interrupted.Value(); ensure
you reference goja.InterruptedError, interrupted.Value(), ctx.Err(), execResult
and timeout when making the change.

In `@router/pkg/sandbox/runtime_qjs.go`:
- Around line 163-165: The lockdown Eval call ignores errors and must fail
closed: capture the error returned by qctx.Eval("lockdown.js", ...) (the
hardening step after qctx.Global().DeleteProperty("eval")) and if non-nil,
propagate it (return the error from the surrounding initialization function) or
log and abort startup so the runtime does not start without the lockdown; ensure
the code around qctx.Eval checks the error and handles it instead of discarding
it.
- Around line 120-133: The async callback handler spawns an unbounded goroutine
per call (the anonymous goroutine that runs res, fnErr = fn.Fn(args) and sends
asyncResult into resultCh), allowing callers to exhaust resources; add a
concurrency cap (e.g., a buffered semaphore channel or a fixed worker pool) that
the goroutine must acquire before executing fn.Fn and release after sending the
result or on panic, so only N async callbacks can run concurrently; use the
existing symbols promise, resultCh, done, and asyncResult to keep behavior
identical while guarding goroutine fan-out (create a package-level or
runtime-level semaphore/worker queue and check/acquire before launching or
dispatching the task).

In `@router/pkg/sandbox/runtime.go`:
- Around line 69-75: NewRuntime currently silently falls back to goja for
unknown RuntimeType values; change it to fail loudly so typos don't disable QJS.
Update NewRuntime (and its switch over RuntimeType/RuntimeTypeQJS) to enumerate
supported cases (e.g., RuntimeTypeQJS and the explicit goja type if present) and
replace the current default branch that returns newGojaRuntime(config) with a
loud failure: either return a clear error (preferred if you can change the
signature) or call panic/log.Fatalf with a formatted message that includes the
invalid runtimeType value; ensure you reference NewRuntime, RuntimeType,
RuntimeTypeQJS, newQJSRuntime and newGojaRuntime when making the change so the
fallback no longer silently picks goja.

In `@router/pkg/yokoclient/client.go`:
- Around line 146-152: The token switch in the client token retrieval (where
c.auth.Type is inspected) rejects "cosmo" causing runtime errors; update the
switch in router/pkg/yokoclient/client.go to treat "cosmo" like "jwt" (e.g., add
a case "cosmo" that calls c.getJWTToken(ctx) or use fallthrough from "cosmo" to
"jwt") so c.getJWTToken(ctx) is invoked for cosmo auth instead of returning the
"unsupported auth type" error; ensure the error branch still covers truly
unsupported values.
- Around line 201-214: The code currently assigns tokenResp.AccessToken into
c.cachedToken and sets c.tokenExpiry without validating the payload; update the
token handling (around tokenResp, c.cachedToken and c.tokenExpiry) to check that
tokenResp.AccessToken is non-empty (and optionally that ExpiresIn is sensible)
before caching—if AccessToken is empty, return a clear error instead of caching
an empty token; only compute earlyExpiry and set c.tokenExpiry after validating
the token value.

In `@router/pkg/yokoclient/mock_openapi.yaml`:
- Around line 1-43: The OpenAPI spec lacks a top-level security requirement so
new paths could be unauthenticated; add a global security requirement (security:
- bearerAuth: []) and define the bearerAuth scheme under
components.securitySchemes (e.g., type: http, scheme: bearer, bearerFormat: JWT)
so the API (including operationId generateQueries) is protected by default while
keeping or overriding per-operation security as needed.

In `@router/pkg/yokoclient/mock.go`:
- Around line 211-227: findRootType currently hardcodes "Query"/"Mutation";
instead read the schema's explicit root operation types and resolve by
definition names: in MockClient.findRootType, inspect m.schema.SchemaDefinitions
(or m.schema.Document.SchemaDefinitions) and their RootOperationTypeDefinitions
to determine the type name (or directly the referenced node ref) for
ast.OperationTypeQuery and ast.OperationTypeMutation, then match that name/ref
against m.schema.RootNodes (or object definitions) rather than the literal
strings. Keep existing behavior as a fallback by using the standard
"Query"/"Mutation" when no schema block is present.

---

Minor comments:
In `@rfc/mcp-agent-mode.md`:
- Around line 40-67: The markdown contains unlabeled fenced code blocks (the
HTTP POST request/response sample and the ASCII AI Agent box) which triggers
MD040; update the fences to include language tags (e.g., change the POST
request/response block to ```http and the ASCII agent/diagram block to a
suitable tag like ```text or ```ascii) and apply the same labeling to the other
unlabeled block later in the file so all unlabeled fenced code blocks are
explicitly tagged.
- Around line 783-785: The doc references outdated filenames and resource names
(router/pkg/mcpserver/agentic_server.go, agentic_descriptions.go, and
agentic://search-api.d.ts) which should be updated to the PR's current naming
(e.g., code_mode_server.go, code_mode_descriptions.go and
code_mode://search-api.d.ts); edit rfc/mcp-agent-mode.md to replace those legacy
strings, ensure mentions of removed APIs (schema.* and validate()) and
implemented functions (generateQuery()) remain accurate, and scan the
surrounding text for any other "agentic_" identifiers to rename to the
code_mode_* equivalents so readers are not misdirected.

In `@router/pkg/mcpserver/code_mode_server_test.go`:
- Around line 431-433: Capture and check the error returned by
json.NewDecoder(r.Body).Decode(&body) instead of ignoring it; if decoding fails
return early (e.g., call http.Error(w, "invalid JSON: "+err.Error(),
http.StatusBadRequest)) or fail the test (t.Fatalf("...: %v", err)) so the mock
handler does not continue to access body["query"] when Decode failed; update the
lines around json.NewDecoder(r.Body).Decode(&body) and the subsequent use of
query to perform this check.

In `@router/pkg/sandbox/runtime_goja.go`:
- Around line 120-121: Replace the simple reassignment call to vm.RunString that
sets Math.random and Date.now with a hardened definition using
Object.defineProperty (called from the same vm.RunString invocation in
runtime_goja.go) so both Math.random and Date.now are defined with non-writable
and non-configurable descriptors that return 0; update the code around the
vm.RunString usage to define the functions via Object.defineProperty on Math and
Date (and ensure Math exists) so sandboxed scripts cannot later reassign or
configure those properties.

In `@router/pkg/yokoclient/mock_openapi.yaml`:
- Around line 69-72: The schema for the response array "queries" (items: $ref:
'#/components/schemas/QueryResult') is currently unbounded; update that array
schema in router/pkg/yokoclient/mock_openapi.yaml to include a sensible maxItems
value (e.g., maxItems: 100) so the contract enforces a response-size cap and
aligns with response-size controls; keep the same items reference and, if
desired, also add minItems or descriptive comment to document the chosen bound.

In `@router/pkg/yokoclient/mock.go`:
- Around line 165-176: hasRequiredArgs currently treats any NonNull argument as
required; update it to consider argument default values so arguments with
defaults are treated as optional. In the hasRequiredArgs function, for each
argRef call m.schema.InputValueDefinitionDefaultValue(argRef) (or the
project-specific accessor for the default) and only treat a NonNull type as
required when that default is absent; i.e. return true when t.TypeKind ==
ast.TypeKindNonNull AND the argument has no default value. Also ensure the
function returns false at the end if no required args are found. Use the
existing symbols: hasRequiredArgs, m.schema.InputValueDefinitionType,
m.schema.Types, and m.schema.InputValueDefinitionDefaultValue (or equivalent) to
locate and implement the change.

---

Nitpick comments:
In `@router/pkg/mcpserver/code_mode_descriptions.go`:
- Around line 10-53: The inline type/contracts in searchToolDescription and
executeToolDescription are duplicated elsewhere; extract the shared TypeScript
contracts (e.g., the QueryResult interface and
GraphQLOptions/GraphQLResponse/GraphQLError definitions) into a single canonical
constant or template (e.g., sharedTypeContracts or graphqlContracts) and
reference or interpolate that constant into both searchToolDescription and
executeToolDescription (and the MCP resource typedefs) so both use the same
source-of-truth; update code that previously declared the interfaces inline to
import/use the shared constant/template to prevent drift.

In `@router/pkg/mcpserver/code_mode_server.go`:
- Around line 607-616: Replace the manual wg.Add(1) / defer wg.Done() pattern
with wg.Go(...) to spawn the goroutine for each prompt: in the loop over prompts
use wg.Go(func() { ... }) to call s.yokoClient.Generate(ctx, prompt, schemaHash)
and send indexedResult on ch; ensure loop variables are captured correctly by
creating local copies (e.g., idx, p or prompt) before using them in the closure
so s.yokoClient.Generate, indexedResult{index: idx, results: results, err: err},
and ch usage refer to the intended per-iteration values.
- Around line 234-244: The tool registration for execute (mcp.NewTool with
executeToolName and mcp.WithString("code")) has an extra stand-alone closing
tuple line containing just "),", causing a formatting inconsistency; edit the
call that constructs the execute tool (the mcp.NewTool(...) invocation used with
executeToolName and s.handleExecute()) to remove the stray closing line and
collapse the parentheses so the mcp.WithString block and the NewTool call are
closed consistently (matching the style used by the search tool registration).
- Around line 667-676: The storeQueryHash function currently clears the entire
s.queryStore map when it reaches maxQueryStoreSize, which can evict all entries
at once; replace the plain map with an LRU cache (e.g.,
github.com/hashicorp/golang-lru/v2) stored on the CodeModeServer struct (replace
s.queryStore and s.queryStoreMu with an lru.Cache instance and its mutex if
needed) and update storeQueryHash to Add the hash→query into the LRU (use
cache.Add(hash, query)) so old entries are evicted incrementally; alternatively,
if the all-clear behavior is intentional, add a clear comment in storeQueryHash
documenting the tradeoff and why simple clear was chosen.
- Around line 104-111: The code currently discards errors returned from
meter.Int64Counter and meter.Float64Histogram; update the initialization around
meter, execCounter and execDuration to check the returned error values and log
any non-nil errors (use the file's existing logger instance, e.g., logger or
s.logger, or fall back to the standard log package) so metric registration
failures are recorded without changing control flow.

In `@router/pkg/sandbox/pool_test.go`:
- Around line 33-37: Replace manual goroutine Add/Done patterns with
sync.WaitGroup.Go: for the loops using wg.Add(1); go func(idx int){ defer
wg.Done(); ... }(i) switch to capturing the loop variable (e.g. idx := i) and
call wg.Go(func(){ ... use idx ... }) and remove the defer wg.Done(); likewise
replace started.Add/started.Done with started.Go(func(){ ... }) and remove the
explicit Done call; ensure any loop-variable usage is copied to a local variable
before calling Go to avoid closure issues.

In `@router/pkg/sandbox/transpiler.go`:
- Around line 69-74: Add a unit test that validates stripAsyncAwait preserves
string literals containing "async " and "await " by calling stripAsyncAwait with
an IIFE async arrow function that includes const msg = "async task" and/or
"await something" in a string and asserting the returned string still contains
those string literals; reference the stripAsyncAwait function in transpiler.go
and create a test (e.g., TestStripAsyncAwait_PreservesStringLiterals) that
covers both raw and minified-like inputs to document and enforce the assumption
that only the async/await keywords are removed, not content inside string
literals.

In `@router/pkg/yokoclient/mock.go`:
- Around line 185-209: Add cached lookup maps to MockClient (e.g., scalarSet
map[string]bool and typeFields map[string][]int) and populate them once when the
schema is loaded/assigned (populate by iterating m.schema.RootNodes and using
m.schema.ScalarTypeDefinitionNameString and
m.schema.ObjectTypeDefinitionNameString to fill the maps); then change isScalar
to check scalarSet first (with an optional fallback to the scan if map is nil)
and change getTypeFields to return typeFields[typeName] (with a nil/empty
fallback), ensuring the cache keys use the exact strings returned by
ScalarTypeDefinitionNameString/ObjectTypeDefinitionNameString to match existing
behavior.

In `@router/scripts/code-mode-demo.sh`:
- Around line 57-65: Replace the fixed sleep calls with readiness probes that
poll the started services until they are actually ready: after starting
subgraphs and before starting the Yoko mock, loop and check their listening
ports or health endpoints (e.g., attempt TCP connect or curl the health URL)
instead of sleep 3; after launching the Yoko mock via the go run command shown
(go run cmd/yoko-mock/main.go --addr localhost:5030 --schema "$SCHEMA_SDL" & and
YOKO_PID=$!), replace sleep 1 with a loop that probes localhost:5030 (TCP
connect or HTTP health check) until it responds or a timeout is reached, and
fail with a clear error if the probe times out; keep using ROUTER_DIR,
SCHEMA_SDL and YOKO_PID names so the change is localized and easy to review.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5bc471 and 99c9dac.

⛔ Files ignored due to path filters (1)
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (34)
  • Makefile
  • demo/pkg/subgraphs/employees/subgraph/schema.resolvers.go
  • rfc/mcp-agent-mode.md
  • router/.gitignore
  • router/cmd/yoko-mock/main.go
  • router/code-mode-demo.config.yaml
  • router/core/router.go
  • router/core/router_config.go
  • router/go.mod
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json
  • router/pkg/mcpserver/code_mode_descriptions.go
  • router/pkg/mcpserver/code_mode_server.go
  • router/pkg/mcpserver/code_mode_server_test.go
  • router/pkg/mcpserver/server.go
  • router/pkg/mcpserver/util.go
  • router/pkg/sandbox/pool.go
  • router/pkg/sandbox/pool_test.go
  • router/pkg/sandbox/runtime.go
  • router/pkg/sandbox/runtime_goja.go
  • router/pkg/sandbox/runtime_qjs.go
  • router/pkg/sandbox/runtime_qjs_test.go
  • router/pkg/sandbox/runtime_test.go
  • router/pkg/sandbox/transpiler.go
  • router/pkg/sandbox/transpiler_test.go
  • router/pkg/yokoclient/client.go
  • router/pkg/yokoclient/client_test.go
  • router/pkg/yokoclient/mock.go
  • router/pkg/yokoclient/mock_openapi.yaml
  • router/pkg/yokoclient/mock_test.go
  • router/scripts/code-mode-demo.sh
  • router/scripts/code-mode-graph.yaml

Comment on lines +67 to +68
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON", err.Error())
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Limit request body size before JSON decode.

Line 67 decodes an unbounded body; a large payload can exhaust memory.

Proposed guard
-		var req generateRequest
-		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		const maxGenerateBodyBytes = 1 << 20 // 1 MiB
+		r.Body = http.MaxBytesReader(w, r.Body, maxGenerateBodyBytes)
+
+		var req generateRequest
+		dec := json.NewDecoder(r.Body)
+		dec.DisallowUnknownFields()
+		if err := dec.Decode(&req); err != nil {
 			writeError(w, http.StatusBadRequest, "Invalid JSON", err.Error())
 			return
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON", err.Error())
const maxGenerateBodyBytes = 1 << 20 // 1 MiB
r.Body = http.MaxBytesReader(w, r.Body, maxGenerateBodyBytes)
var req generateRequest
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
if err := dec.Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "Invalid JSON", err.Error())
return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/cmd/yoko-mock/main.go` around lines 67 - 68, The handler decodes
r.Body with json.NewDecoder(r.Body).Decode(&req) without limiting size; wrap the
request body with http.MaxBytesReader (or io.LimitReader) before decoding to
enforce a safe max (e.g., 1MB), check the decode error for io.ErrUnexpectedEOF
or http.ErrBodyReadAfterClose/“request body too large” and return a 413 or 400
via writeError if the body exceeds the limit or JSON is invalid; update the code
around json.NewDecoder/Decode (and r.Body usage) to use the limited reader and
handle the oversized-body error path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — yoko-mock is a development/testing tool, not production infrastructure. Body size limits are unnecessary here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood — I appreciate the clarification. Since yoko-mock is a dev/test utility, the additional hardening isn't necessary. Thanks for the context!


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

Comment on lines +83 to +86
if err != nil {
log.Printf("[ERROR] %v (took %s)", err, elapsed)
writeError(w, http.StatusInternalServerError, "Query generation failed", err.Error())
return
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid returning internal generator details to API clients.

Line 85 exposes err.Error() to callers. With Line 145, parse errors can include raw Claude output, which leaks internal data.

Proposed redaction
-		if err != nil {
-			log.Printf("[ERROR] %v (took %s)", err, elapsed)
-			writeError(w, http.StatusInternalServerError, "Query generation failed", err.Error())
+		if err != nil {
+			log.Printf("[ERROR] query generation failed: %v (took %s)", err, elapsed)
+			writeError(w, http.StatusInternalServerError, "Query generation failed", "")
 			return
 		}
@@
-	if err := json.Unmarshal([]byte(text), &queries); err != nil {
-		return nil, fmt.Errorf("failed to parse claude output as JSON: %w\nRaw output:\n%s", err, text)
+	if err := json.Unmarshal([]byte(text), &queries); err != nil {
+		return nil, fmt.Errorf("failed to parse claude output as JSON: %w", err)
 	}

Also applies to: 144-146

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/cmd/yoko-mock/main.go` around lines 83 - 86, The handler currently
returns internal error text (err.Error()) to API clients via writeError (seen
around the writeError(...) calls at the top block and the similar calls at lines
~144-146), which can leak raw generator/Claude output; change these writeError
calls to return a generic client-facing message (e.g., "internal server error"
or "query generation failed") and avoid including err.Error() in the HTTP
response. Continue to log the full error and elapsed time with log.Printf (or
processLogger) for debugging, and optionally generate an internal error ID to
return to the client so logs can be correlated without exposing raw error
contents. Ensure this sanitization is applied to all places that call writeError
with err.Error() (including the parse error path).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — yoko-mock is a development/testing tool. Exposing error details is useful for debugging during development.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood! Since yoko-mock is a development/testing tool, detailed error exposure makes sense for debugging purposes. Thanks for the clarification.


✏️ Learnings added
Learnt from: jensneuse
Repo: wundergraph/cosmo PR: 2575
File: router/cmd/yoko-mock/main.go:83-86
Timestamp: 2026-03-02T19:11:26.502Z
Learning: router/cmd/yoko-mock is a development/testing tool (not production code) where exposing detailed error messages including raw Claude output is acceptable and useful for debugging during development.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

@@ -75,12 +78,13 @@ require (
github.com/hashicorp/go-plugin v1.6.3
github.com/iancoleman/strcase v0.3.0
github.com/klauspost/compress v1.18.0
github.com/mark3labs/mcp-go v0.36.0
github.com/mark3labs/mcp-go v0.43.2
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== mcp-go versions across all go.mod files ==="
fd -t f go.mod -x sh -c 'echo "--- $1 ---"; rg "github.com/mark3labs/mcp-go" "$1" || echo "  (not found)"' sh {}

echo
echo "=== mcp-go imports in router code (if any) ==="
rg --type go "github.com/mark3labs/mcp-go|mcp\\.Server|mcp\\.Client" --max-count=20 2>/dev/null || echo "  (no imports found)"

Repository: wundergraph/cosmo

Length of output: 1389


🌐 Web query:

github.com/mark3labs/mcp-go v0.36.0 vs v0.43.2 breaking changes API differences

💡 Result:

Breaking changes between v0.36.0v0.43.2

  1. Result.Meta type changed (v0.37.0)
  • Before (≤ v0.36.0): result.Meta was map[string]any
  • After (≥ v0.37.0): result.Meta is *mcp.Meta (with AdditionalFields)
    Migration example is shown in the v0.37.0 release notes. [1]
  1. OAuth TokenStore interface method signatures changed (v0.40.0)
  • feat!: adds context.Context to TokenStore methods, so any custom TokenStore implementation must update its method signatures accordingly. [2]
  1. mcp.Implementation gained a Title field (v0.43.0)
  • If your code uses unkeyed struct literals (positional composite literals) for mcp.Implementation, adding a field can cause compile errors / misassignment.
  • Fix: switch to keyed fields in the literal. [3]

Other notable API / behavior differences (not necessarily “breaking”, but relevant)

  • New “Roots” support in HTTP + stdio clients (v0.43.0) (new requests/flows you may want to adopt). [3]
  • Request-based session management hooks via SessionIdManagerResolver (v0.43.0). [3]
  • v0.43.2 itself is bugfix-only (notification/tool-call handling; schema unmarshal fix). [4]

Sources

[1] v0.37.0 release (Breaking change: Result.Meta*Meta) (newreleases.io)
[2] v0.40.0 release (feat!: add context.Context to TokenStore methods) (newreleases.io)
[3] v0.43.0 release (adds Implementation.Title, Roots, SessionIdManagerResolver, etc.) (github.com)
[4] v0.43.2 release notes (newreleases.io)

Citations:


🌐 Web query:

mark3labs mcp-go v0.43.2 release notes changelog

💡 Result:

mark3labs/mcp-go v0.43.2 — changelog / release notes (released 2025-12-04)

What’s changed

Full changelog compare: v0.43.1...v0.43.2 [1]

Sources

Citations:


🌐 Web query:

mark3labs mcp-go v0.38.0 v0.39.0 v0.40.0 regression issues

💡 Result:

mark3labs/mcp-go regressions / breaking changes around v0.38.0–v0.40.0

  • v0.38.0 (released Aug 20, 2025): shipped fixes for stdio transport concurrency (race condition on concurrent tool calls) and CallToolResult JSON marshal/unmarshal correctness—so if you were seeing flaky stdio behavior or incorrect tool-result JSON before v0.38, upgrading to v0.38 addressed those. [1]

  • v0.39.0 (released Sep 2, 2025): changed stdio transport to use bufio.Scanner “to avoid panic when … outputs a long line”. This is a common source of regressions with large JSON-RPC messages because bufio.Scanner has practical token-size limits and can fail/truncate on large payloads. [2]

    • This was later effectively confirmed/fixed when v0.41.1 replaced bufio.Scanner with bufio.Reader “to support large messages”. If you hit “large message” truncation/failures after moving to v0.39.x, upgrading to >= v0.41.1 (or backporting that change) is the main remediation. [4]
  • v0.40.0 (released Sep 19, 2025): includes a breaking API change: TokenStore methods gained a context.Context parameter (feat!). Any custom TokenStore implementations will fail to compile until updated—this is often experienced as a “regression” during upgrade even though it’s an intentional breaking change. [3]

Sources: GitHub release notes for v0.38.0 [1], v0.39.0 [2], v0.40.0 [3], and the later large-message fix in v0.41.1 [4].


Urgent: router/go.mod mcp-go v0.43.2 introduces multiple breaking changes with no visible code migration.

router/go.mod line 81 jumps from the project's baseline v0.36.0 (used in router-tests/go.mod and demo/go.mod) to v0.43.2, which includes confirmed breaking API changes:

  • v0.37.0: Result.Meta type changed from map[string]any*mcp.Meta
  • v0.40.0: TokenStore interface methods now require context.Context parameter
  • v0.43.0: mcp.Implementation gained Title field (breaks unkeyed struct literals)

Router code actively imports and uses mcp-go/mcp and mcp-go/server (in server.go, code_mode_server.go). The diff provides no evidence that these breaking changes were handled. Either downgrade to v0.36.0 and migrate router-tests in a separate PR, or provide a detailed code migration diff showing all API call updates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/go.mod` at line 81, The module bump to github.com/mark3labs/mcp-go
v0.43.2 introduces breaking changes; either pin back to the known-compatible
v0.36.0 in router/go.mod or migrate router code to the new API: update all
usages of Result.Meta to accept/handle *mcp.Meta (change types and any map
access accordingly), update any TokenStore implementations and calls (in
server.go and code_mode_server.go) to add context.Context as the first parameter
to the affected methods, and fix construction of mcp.Implementation values by
switching to keyed struct literals and populating the new Title field where
applicable; search for Result.Meta, TokenStore, and mcp.Implementation usages in
server.go and code_mode_server.go to make these targeted edits.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action needed — we use the mcp-go version required by this project.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood! I acknowledge that the version bump to v0.43.2 is intentional for this project's requirements. Since the new MCP Code Mode functionality was developed with this version in mind, the API changes are already incorporated into the implementation.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2438
File: router-tests/go.mod:15-15
Timestamp: 2026-01-05T11:53:58.709Z
Learning: For mcp-go dependency in wundergraph/cosmo, patch releases may not have formal release notes published. Tag diffs should be reviewed to verify patch-level changes contain no API surface changes or breaking behavior before adopting new patch versions.

Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2252
File: router-tests/telemetry/telemetry_test.go:9684-9693
Timestamp: 2025-10-01T20:39:16.113Z
Learning: Repo preference: In router-tests/telemetry/telemetry_test.go, keep strict > 0 assertions for request.operation.*Time (parsingTime, normalizationTime, validationTime, planningTime) in telemetry-related tests; do not relax to >= 0 unless CI flakiness is observed.

Comment on lines +1 to +43
openapi: "3.0.3"
info:
title: Yoko Query Generation API
description: REST API for generating GraphQL queries from natural language prompts.
version: "1.0.0"
servers:
- url: https://yoko.example.com
description: Production
paths:
/v1/generate:
post:
operationId: generateQueries
summary: Generate GraphQL queries from a natural language prompt
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateRequest'
responses:
'200':
description: Successfully generated queries
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateResponse'
'400':
description: Bad request or could not generate query
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
components:
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Set a global security default for the API spec.

Line 41 secures the current operation, but without top-level security, future paths can be added unauthenticated by mistake.

Proposed OpenAPI hardening
 servers:
   - url: https://yoko.example.com
     description: Production
+security:
+  - bearerAuth: []
 paths:
   /v1/generate:
     post:
@@
-      security:
-        - bearerAuth: []
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
openapi: "3.0.3"
info:
title: Yoko Query Generation API
description: REST API for generating GraphQL queries from natural language prompts.
version: "1.0.0"
servers:
- url: https://yoko.example.com
description: Production
paths:
/v1/generate:
post:
operationId: generateQueries
summary: Generate GraphQL queries from a natural language prompt
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateRequest'
responses:
'200':
description: Successfully generated queries
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateResponse'
'400':
description: Bad request or could not generate query
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
security:
- bearerAuth: []
components:
openapi: "3.0.3"
info:
title: Yoko Query Generation API
description: REST API for generating GraphQL queries from natural language prompts.
version: "1.0.0"
servers:
- url: https://yoko.example.com
description: Production
security:
- bearerAuth: []
paths:
/v1/generate:
post:
operationId: generateQueries
summary: Generate GraphQL queries from a natural language prompt
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateRequest'
responses:
'200':
description: Successfully generated queries
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateResponse'
'400':
description: Bad request or could not generate query
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
🧰 Tools
🪛 Checkov (3.2.334)

[high] 1-102: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/mock_openapi.yaml` around lines 1 - 43, The OpenAPI
spec lacks a top-level security requirement so new paths could be
unauthenticated; add a global security requirement (security: - bearerAuth: [])
and define the bearerAuth scheme under components.securitySchemes (e.g., type:
http, scheme: bearer, bearerFormat: JWT) so the API (including operationId
generateQueries) is protected by default while keeping or overriding
per-operation security as needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — this is a mock OpenAPI spec for testing, not a deployed API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood! Since this is a mock spec for local testing and development, the security hardening isn't necessary. Thanks for the clarification.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Comment on lines +211 to +227
func (m *MockClient) findRootType(opType ast.OperationType) (int, bool) {
for _, node := range m.schema.RootNodes {
if node.Kind == ast.NodeKindObjectTypeDefinition {
name := m.schema.ObjectTypeDefinitionNameString(node.Ref)
switch opType {
case ast.OperationTypeQuery:
if name == "Query" {
return node.Ref, true
}
case ast.OperationTypeMutation:
if name == "Mutation" {
return node.Ref, true
}
}
}
}
return -1, false
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and understand the context
cd router/pkg/yokoclient && ls -la

Repository: wundergraph/cosmo

Length of output: 495


🏁 Script executed:

# Examine the mock.go file around the flagged lines
head -250 mock.go | tail -60

Repository: wundergraph/cosmo

Length of output: 127


🏁 Script executed:

# Check if there's a schema definition file to understand the data structure
find router/pkg -name "*schema*" -type f | head -20

Repository: wundergraph/cosmo

Length of output: 363


🏁 Script executed:

# Look for imports and dependencies in mock.go
head -30 mock.go

Repository: wundergraph/cosmo

Length of output: 127


🏁 Script executed:

# Read the mock.go file from the repository root
head -30 router/pkg/yokoclient/mock.go

Repository: wundergraph/cosmo

Length of output: 1038


🏁 Script executed:

# Read the specific lines mentioned in the review (211-227)
sed -n '211,227p' router/pkg/yokoclient/mock.go

Repository: wundergraph/cosmo

Length of output: 526


🏁 Script executed:

# Get more context around the function
sed -n '200,240p' router/pkg/yokoclient/mock.go

Repository: wundergraph/cosmo

Length of output: 1121


🏁 Script executed:

# Check the schema definition structure
find . -path ./dist -prune -o -name "*.go" -type f -exec grep -l "RootNodes\|RootOperationType" {} \; | head -10

Repository: wundergraph/cosmo

Length of output: 712


🏁 Script executed:

# Search for RootOperationTypeDefinition or SchemaDefinition in the codebase
rg "RootOperationTypeDefinition|SchemaDefinition" --type go | head -30

Repository: wundergraph/cosmo

Length of output: 1320


🏁 Script executed:

# Look for how the schema is typically structured and initialized
rg "schema\s*{" router/pkg/yokoclient/ --type go -A 3 -B 3

Repository: wundergraph/cosmo

Length of output: 144


🏁 Script executed:

# Check test cases in mock_test.go to understand expected behavior
cat router/pkg/yokoclient/mock_test.go

Repository: wundergraph/cosmo

Length of output: 5301


🏁 Script executed:

# Search for any existing root operation type handling in the client
rg "OperationType|schema.*query|schema.*mutation" router/pkg/yokoclient/ --type go

Repository: wundergraph/cosmo

Length of output: 789


🏁 Script executed:

# Check if the ast package has SchemaDefinition or root operation types
rg "SchemaDefinition|RootOperation" --type go | grep -E "(graphql-go-tools|ast\\.)" | head -20

Repository: wundergraph/cosmo

Length of output: 257


🏁 Script executed:

# Look for how other parts of the codebase handle schema root type definitions
rg "QueryRoot|MutationRoot|SubscriptionRoot" --type go | head -20

Repository: wundergraph/cosmo

Length of output: 1554


🏁 Script executed:

# Check the graphql-go-tools library usage in the codebase
find . -path "*/graphql-go-tools*" -prune -o -name "*.go" -exec grep -l "ast\\.Document" {} \; | xargs grep -l "Query\|Mutation" | head -10

Repository: wundergraph/cosmo

Length of output: 449


🏁 Script executed:

# Look at the actual AST Document structure being used
rg "ast\.Document" router/pkg/yokoclient/mock.go -B 5 -A 10 | head -40

Repository: wundergraph/cosmo

Length of output: 774


🏁 Script executed:

# Check if there's documentation or examples showing schema definition handling
find . -name "*.go" -type f -exec grep -l "schema.*query.*:" {} \; | head -5

Repository: wundergraph/cosmo

Length of output: 253


🏁 Script executed:

# Search for how schema definitions are accessed in the graphql-go-tools library
rg "SchemaDefinition|GetSchemaDefinition" --type go | head -20

Repository: wundergraph/cosmo

Length of output: 1320


🏁 Script executed:

# Look for examples of how root operation types are resolved in the codebase
rg "Query.*type|Mutation.*type|OperationType" router/ --type go -B 2 -A 2 | head -50

Repository: wundergraph/cosmo

Length of output: 2741


🏁 Script executed:

# Check the actual graphql-go-tools AST Document structure or documentation
find . -type f -name "*.go" -exec grep -l "ast.Document" {} \; | xargs grep "SchemaDefinition\|RootOperation" | head -10

Repository: wundergraph/cosmo

Length of output: 984


🏁 Script executed:

# Look for any recent changes or TODOs related to schema handling
rg "TODO|FIXME|BUG" router/pkg/yokoclient/ --type go

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Check what fields are available on ast.Document by searching for usages
rg "m\.schema\." router/pkg/yokoclient/mock.go --type go

Repository: wundergraph/cosmo

Length of output: 1146


🏁 Script executed:

# Search for SchemaDefinition usage in the graphql-go-tools or how root types should be resolved
rg "SchemaDefinition\|GetQuery\|GetMutation" --type go | head -20

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Check if there are any examples in the codebase showing custom root type names
rg "schema.*{" router/ --type go -A 5 | grep -E "(query|mutation|subscription)" | head -20

Repository: wundergraph/cosmo

Length of output: 142


🏁 Script executed:

# Look at how operation_processor handles root types
grep -n "Query\|Mutation" router/core/operation_processor.go | head -20

Repository: wundergraph/cosmo

Length of output: 1916


🏁 Script executed:

# Create a test schema with custom root type names to understand the problem
python3 << 'EOF'
# Check if the current implementation would fail with custom root type names
test_schema_custom = """
schema {
  query: RootQuery
  mutation: RootMutation
}

type RootQuery {
  hello: String
}

type RootMutation {
  createUser(name: String!): String
}
"""

test_schema_standard = """
schema {
  query: Query
  mutation: Mutation
}

type Query {
  hello: String
}

type Mutation {
  createUser(name: String!): String
}
"""

print("Custom root type names schema:")
print(test_schema_custom)
print("\n" + "="*50)
print("Standard root type names schema:")
print(test_schema_standard)
print("\nIssue: The current findRootType() only looks for 'Query' and 'Mutation' names")
print("It would FAIL for the custom schema since RootQuery and RootMutation are not found")
EOF

Repository: wundergraph/cosmo

Length of output: 626


🏁 Script executed:

# Check if ast.Document has SchemaDefinition field
rg "ast\.Document\s*struct" . --type go -A 30 | head -50

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Look for how the schema root types should be accessed properly
rg "QueryRoot|MutationRoot|SchemaDefinition" . --type go | grep -v "vendor\|dist\|node_modules" | head -20

Repository: wundergraph/cosmo

Length of output: 2179


🏁 Script executed:

# Look at what methods are available on ast.Document in the codebase
rg "m\.schema\.[A-Za-z]+" router/pkg/yokoclient/mock.go --type go -o | sort | uniq

Repository: wundergraph/cosmo

Length of output: 387


🏁 Script executed:

# Check if there's a SchemaDefinitions or similar field we can access
rg "SchemaDefinitions\|QueryRootTypeName\|MutationRootTypeName" . --type go | head -20

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Look at the full mock.go to see all uses of the schema object
cat router/pkg/yokoclient/mock.go

Repository: wundergraph/cosmo

Length of output: 6380


🏁 Script executed:

# Search for how the graphql-go-tools library handles SchemaDefinitions
find . -type f -name "*.go" -exec grep -l "SchemaDefinitions\|Document.*Schema" {} \; | head -10

Repository: wundergraph/cosmo

Length of output: 386


🏁 Script executed:

# Check the go.mod to see the exact version of graphql-go-tools being used
cat go.mod | grep graphql-go-tools

Repository: wundergraph/cosmo

Length of output: 99


🏁 Script executed:

# Look for any usage patterns of schema definitions in the codebase
rg "SchemaDefinition|QueryRootTypeName" . --type go | head -30

Repository: wundergraph/cosmo

Length of output: 1350


🏁 Script executed:

# Create a test to see what happens with custom root type names
cat > /tmp/test_root_types.go << 'EOF'
package main

import (
	"fmt"
)

// The issue: findRootType() checks if name == "Query" or name == "Mutation"
// But GraphQL schemas can define custom root types via: schema { query: RootQuery }
// In that case, the root type object would be named "RootQuery", not "Query"

// Example schemas:
const standardSchema = `
schema {
  query: Query
  mutation: Mutation
}
type Query { hello: String }
type Mutation { update: String }
`

const customSchema = `
schema {
  query: RootQuery
  mutation: RootMutation
}
type RootQuery { hello: String }
type RootMutation { update: String }
`

const noSchemaDirective = `
type Query { hello: String }
type Mutation { update: String }
`

func main() {
	fmt.Println("Standard Schema (works):")
	fmt.Println(standardSchema)
	fmt.Println("\nCustom Root Types Schema (would FAIL):")
	fmt.Println(customSchema)
	fmt.Println("\nNo Schema Directive (would FAIL):")
	fmt.Println(noSchemaDirective)
	fmt.Println("\nThe current findRootType() hardcodes checking for 'Query' and 'Mutation' names.")
	fmt.Println("It should instead read from the schema directive to get the actual root type names.")
}
EOF
python3 << 'EOFPY'
# The real issue is that the code needs to:
# 1. Look for a SchemaDefinition in the ast.Document
# 2. Extract the query, mutation, subscription root type names from it
# 3. Use those names instead of hardcoded "Query"/"Mutation"

print("Issue confirmed:")
print("- findRootType() only matches hardcoded 'Query' and 'Mutation' names")
print("- Fails for schemas using: schema { query: RootQuery mutation: RootMutation }")
print("- Should read root type names from SchemaDefinition instead")
EOFPY

Repository: wundergraph/cosmo

Length of output: 284


🏁 Script executed:

# Check if ast.Document has SchemaDefinitions field
rg "\.SchemaDefinitions\s*\[" . --type go | head -10

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Look for examples of accessing schema definitions in the codebase
rg "SchemaDefinition\|QueryRootTypeName\|GetQuery" router/ --type go -B 2 -A 2 | head -50

Repository: wundergraph/cosmo

Length of output: 43


🏁 Script executed:

# Check if there are tests with custom root type names
rg "schema.*query.*:|schema.*mutation.*:" router/ --type go | head -20

Repository: wundergraph/cosmo

Length of output: 6650


🏁 Script executed:

# Look at the plan_generator or operation_processor to see how they handle root types
grep -n "RootNodes\|RootType\|Query\|Mutation" router/core/operation_processor.go | head -30

Repository: wundergraph/cosmo

Length of output: 2960


🏁 Script executed:

# Search for any references to how to properly get root operation types
rg "QueryRoot|SubscriptionRoot|operation.*root\|root.*operation" router/ --type go -i | head -20

Repository: wundergraph/cosmo

Length of output: 572


🏁 Script executed:

# Check the ast package in graphql-go-tools by looking at what's imported
grep -A 50 "import" router/pkg/yokoclient/mock.go | head -60

Repository: wundergraph/cosmo

Length of output: 1536


🏁 Script executed:

# Look for web documentation or comments about the ast.Document structure
web_search
graphql-go-tools ast.Document SchemaDefinition root types

Repository: wundergraph/cosmo

Length of output: 166


🌐 Web query:

graphql-go-tools ast.Document SchemaDefinition root operation types

💡 Result:

In graphql-go-tools (v2), the root operation types live in the AST as a two-step indirection:

  1. ast.Document.SchemaDefinitions[] contains ast.SchemaDefinition nodes. Each SchemaDefinition has a RootOperationTypeDefinitions field of type RootOperationTypeDefinitionList, which is basically a list of integer refs (Refs []int). Those refs point into… [1]

  2. ast.Document.RootOperationTypeDefinitions[] which stores the actual ast.RootOperationTypeDefinition entries (each one is an (OperationType, NamedType) pair, e.g. query: Query). The Document struct in v2 explicitly has both slices: SchemaDefinitions []SchemaDefinition and RootOperationTypeDefinitions []RootOperationTypeDefinition. [2]

So, conceptually:

  • schema { query: Query mutation: Mutation subscription: Subscription }
  • becomes:
    • a SchemaDefinition whose RootOperationTypeDefinitions.Refs = []int{...}
    • and each ref indexes document.RootOperationTypeDefinitions[ref]

Docs/example that show constructing it this way (create RootOperationTypeDefinition, append it to doc.RootOperationTypeDefinitions, then reference it from SchemaDefinition.RootOperationTypeDefinitions.Refs) are in the ast package docs. [1]

Sources: [1] [2]


Resolve root operation types from schema definitions, not hardcoded names.

findRootType() only matches object names "Query" and "Mutation". GraphQL allows custom root type names via schema { query: RootQuery mutation: RootMutation }. The method should read from ast.Document.SchemaDefinitions and RootOperationTypeDefinitions instead of hardcoding type names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/mock.go` around lines 211 - 227, findRootType currently
hardcodes "Query"/"Mutation"; instead read the schema's explicit root operation
types and resolve by definition names: in MockClient.findRootType, inspect
m.schema.SchemaDefinitions (or m.schema.Document.SchemaDefinitions) and their
RootOperationTypeDefinitions to determine the type name (or directly the
referenced node ref) for ast.OperationTypeQuery and ast.OperationTypeMutation,
then match that name/ref against m.schema.RootNodes (or object definitions)
rather than the literal strings. Keep existing behavior as a fallback by using
the standard "Query"/"Mutation" when no schema block is present.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — mock implementation for testing purposes. The hardcoded root types are sufficient for the mock.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood! For a mock implementation used in testing, the simplified approach with hardcoded root types is perfectly reasonable. Thank you for clarifying the scope and intent.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

jensneuse and others added 2 commits March 2, 2026 17:12
Staticcheck S1039: fmt.Sprintf with no format directives is unnecessary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- runtime.go: explicitly handle RuntimeTypeGoja, default to qjs for
  unknown types instead of silently falling back to goja
- runtime_qjs.go: check lockdown eval error instead of ignoring it
- pool_test.go: actually call trackConcurrency() in concurrency test
- yokoclient/client.go: accept "cosmo" auth type as alias for JWT,
  validate non-empty access_token before caching
- config.schema.json: add missing max_input_size_bytes to sandbox schema,
  add token_endpoint/client_id/client_secret to auth schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
router/pkg/config/config.schema.json (2)

2491-2520: ⚠️ Potential issue | 🟠 Major

Enforce auth credentials conditionally by auth.type.

Line 2491-2519 allows type: "cosmo" without token_endpoint/client_id/client_secret, and type: "static" without static_token. This passes schema validation but can fail at runtime.

Suggested schema patch
                 "auth": {
                   "type": "object",
                   "description": "Authentication configuration for the query generation service.",
                   "additionalProperties": false,
+                  "required": ["type"],
                   "properties": {
                     "type": {
                       "type": "string",
                       "enum": ["static", "cosmo"],
                       "default": "static",
                       "description": "Authentication type. 'static' uses a fixed token, 'cosmo' uses the Cosmo platform token."
                     },
@@
                     "client_secret": {
                       "type": "string",
                       "description": "Client secret for client credentials auth flow."
                     }
-                  }
+                  },
+                  "allOf": [
+                    {
+                      "if": {
+                        "properties": { "type": { "const": "static" } }
+                      },
+                      "then": {
+                        "required": ["static_token"]
+                      }
+                    },
+                    {
+                      "if": {
+                        "properties": { "type": { "const": "cosmo" } }
+                      },
+                      "then": {
+                        "required": ["token_endpoint", "client_id", "client_secret"]
+                      }
+                    }
+                  ]
                 },

Based on learnings: Required-field validation for security/auth configuration is handled at the JSON schema level in this repository.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/config/config.schema.json` around lines 2491 - 2520, The auth
schema currently allows mismatched credentials for auth.type; update the "auth"
object to enforce conditional required fields: add JSON Schema conditional
blocks using "if": {"properties": {"type": {"const": "static"}}} with "then":
{"required": ["static_token"]} and another "if": {"properties": {"type":
{"const": "cosmo"}}} with "then": {"required":
["token_endpoint","client_id","client_secret"]}; retain existing property
definitions ("type","static_token","token_endpoint","client_id","client_secret")
and keep "additionalProperties": false so validation fails when required fields
for the selected auth.type are missing.

2454-2472: ⚠️ Potential issue | 🟠 Major

Add hard bounds for sandbox resource limits.

Line 2454, Line 2459, Line 2464, and Line 2469 define limit fields but allow unbounded/invalid integers. Please enforce at least minimum constraints (and ideally maximums aligned with runtime caps) at schema level.

Suggested schema patch
                 "max_memory_mb": {
                   "type": "integer",
                   "default": 16,
+                  "minimum": 1,
                   "description": "Maximum memory in MB available to the sandbox runtime."
                 },
                 "max_fuel": {
                   "type": "integer",
                   "default": 10000000,
+                  "minimum": 1,
                   "description": "Maximum CPU fuel for sandbox execution."
                 },
                 "max_input_size_bytes": {
                   "type": "integer",
                   "default": 65536,
+                  "minimum": 1,
                   "description": "Maximum size in bytes of the sandbox input code."
                 },
                 "max_output_size_bytes": {
                   "type": "integer",
                   "default": 1048576,
+                  "minimum": 1,
                   "description": "Maximum size in bytes of the sandbox output."
                 }

Based on learnings: In the Cosmo router project, parameter validation is expected at the JSON schema level (with minimum constraints used to prevent invalid values).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/config/config.schema.json` around lines 2454 - 2472, The JSON
schema properties max_memory_mb, max_fuel, max_input_size_bytes, and
max_output_size_bytes currently lack bounds; update each property in the schema
to include a "minimum" (to prevent zero/negative/invalid values) and add
sensible "maximum" caps that align with runtime limits (for example,
max_memory_mb: minimum 1 and a runtime-aligned maximum, max_fuel: minimum 1 and
runtime cap, max_input_size_bytes: minimum 1 and an upper cap,
max_output_size_bytes: minimum 1 and an upper cap). Modify the entries for
"max_memory_mb", "max_fuel", "max_input_size_bytes", and "max_output_size_bytes"
to include these "minimum" and "maximum" numeric constraints so validation is
enforced at the schema level.
🧹 Nitpick comments (1)
router/pkg/yokoclient/client.go (1)

146-152: Normalize auth.type before switching.

Line [146] currently requires exact casing/spacing. Normalizing avoids config footguns (" JWT " / "JWT").

♻️ Suggested hardening
 func (c *Client) getToken(ctx context.Context) (string, error) {
-	switch c.auth.Type {
+	authType := strings.ToLower(strings.TrimSpace(c.auth.Type))
+	switch authType {
 	case "static", "":
 		return c.auth.StaticToken, nil
 	case "jwt", "cosmo":
 		return c.getJWTToken(ctx)
 	default:
-		return "", fmt.Errorf("unsupported auth type: %s", c.auth.Type)
+		return "", fmt.Errorf("unsupported auth type: %s", c.auth.Type)
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/yokoclient/client.go` around lines 146 - 152, The switch on
c.auth.Type is case/whitespace-sensitive; normalize the value before switching
by creating a local normalized variable (e.g., typ :=
strings.ToLower(strings.TrimSpace(c.auth.Type))) and switch on that instead of
c.auth.Type; update the switch cases in the function that contains the switch
(referenced by c.auth.Type and c.getJWTToken) to use the normalized variable and
keep the same branches ("static","", "jwt","cosmo") and default error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/core/router.go`:
- Around line 1021-1034: The code starts a Code Mode server (codeModeSrv via
mcpserver.NewCodeModeServer and codeModeSrv.Start()) and assigns it to
r.codeModeServer, but if later bootstrap steps fail the server may remain
running; add bootstrap-level rollback cleanup that ensures any started auxiliary
servers are stopped on subsequent bootstrap errors by: tracking the started
server (codeModeSrv and r.codeModeServer), and in the outer bootstrap error path
ensure you call Stop(ctx) on codeModeSrv (or r.codeModeServer) and log warnings
on Stop errors (similar to the existing inner cleanup), so any failure after
assigning r.codeModeServer triggers a deterministic stop and prevents leaks.
- Around line 993-1000: The code is forcing wildcard origins by setting
cfg.AllowOrigins = []string{"*"} which overrides user policy; remove that
assignment and preserve r.corsOptions.AllowOrigins when copying cfg, only ensure
required MCP methods and headers are present (set cfg.AllowMethods to include
"GET","PUT","POST","DELETE","OPTIONS" and append the MCP headers to
cfg.AllowHeaders), then assign corsConfig = &cfg; also ensure you do not
introduce a wildcard origin when credentials are enabled (leave AllowOrigins
untouched instead of forcing "*").

In `@router/pkg/config/config.go`:
- Around line 1084-1089: The QueryGenerationAuth struct's fields (Type,
TokenEndpoint, ClientID, ClientSecret, StaticToken) lack env struct tags so
env.Parse cannot populate them; update the QueryGenerationAuth definition to add
appropriate `env:"..."` tags (and keep `envDefault` for Type) matching the field
names used elsewhere (e.g., `env:"QUERY_GENERATION_AUTH_TYPE"`,
`env:"QUERY_GENERATION_TOKEN_ENDPOINT"`, `env:"QUERY_GENERATION_CLIENT_ID"`,
`env:"QUERY_GENERATION_CLIENT_SECRET"`, and `env:"YOKO_TOKEN"` for StaticToken)
so that env.Parse called in the QueryGenerationConfiguration flow will inject
values into QueryGenerationAuth. Ensure tags follow the same naming convention
used by other nested configs like MCPSessionConfig and MCPStorageConfig.

---

Duplicate comments:
In `@router/pkg/config/config.schema.json`:
- Around line 2491-2520: The auth schema currently allows mismatched credentials
for auth.type; update the "auth" object to enforce conditional required fields:
add JSON Schema conditional blocks using "if": {"properties": {"type": {"const":
"static"}}} with "then": {"required": ["static_token"]} and another "if":
{"properties": {"type": {"const": "cosmo"}}} with "then": {"required":
["token_endpoint","client_id","client_secret"]}; retain existing property
definitions ("type","static_token","token_endpoint","client_id","client_secret")
and keep "additionalProperties": false so validation fails when required fields
for the selected auth.type are missing.
- Around line 2454-2472: The JSON schema properties max_memory_mb, max_fuel,
max_input_size_bytes, and max_output_size_bytes currently lack bounds; update
each property in the schema to include a "minimum" (to prevent
zero/negative/invalid values) and add sensible "maximum" caps that align with
runtime limits (for example, max_memory_mb: minimum 1 and a runtime-aligned
maximum, max_fuel: minimum 1 and runtime cap, max_input_size_bytes: minimum 1
and an upper cap, max_output_size_bytes: minimum 1 and an upper cap). Modify the
entries for "max_memory_mb", "max_fuel", "max_input_size_bytes", and
"max_output_size_bytes" to include these "minimum" and "maximum" numeric
constraints so validation is enforced at the schema level.

---

Nitpick comments:
In `@router/pkg/yokoclient/client.go`:
- Around line 146-152: The switch on c.auth.Type is case/whitespace-sensitive;
normalize the value before switching by creating a local normalized variable
(e.g., typ := strings.ToLower(strings.TrimSpace(c.auth.Type))) and switch on
that instead of c.auth.Type; update the switch cases in the function that
contains the switch (referenced by c.auth.Type and c.getJWTToken) to use the
normalized variable and keep the same branches ("static","", "jwt","cosmo") and
default error handling.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99c9dac and 51a943b.

⛔ Files ignored due to path filters (2)
  • router-tests/go.sum is excluded by !**/*.sum
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • router-tests/go.mod
  • router/core/router.go
  • router/core/router_config.go
  • router/go.mod
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json
  • router/pkg/mcpserver/code_mode_server_test.go
  • router/pkg/sandbox/pool_test.go
  • router/pkg/sandbox/runtime.go
  • router/pkg/sandbox/runtime_qjs.go
  • router/pkg/yokoclient/client.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • router/core/router_config.go
  • router/pkg/sandbox/runtime_qjs.go
  • router/pkg/sandbox/pool_test.go
  • router/pkg/mcpserver/code_mode_server_test.go

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 0.17406% with 1147 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.93%. Comparing base (e735694) to head (a26816d).
⚠️ Report is 51 commits behind head on main.

Files with missing lines Patch % Lines
router/pkg/mcpserver/code_mode_server.go 0.00% 476 Missing ⚠️
router/pkg/sandbox/runtime_qjs.go 0.00% 206 Missing ⚠️
router/pkg/yokoclient/mock.go 0.00% 123 Missing ⚠️
router/internal/llmcli/race.go 0.00% 99 Missing ⚠️
router/pkg/yokoclient/client.go 0.00% 94 Missing ⚠️
router/pkg/mcpserver/execute_tsgo_validator.go 0.00% 49 Missing ⚠️
router/core/router.go 2.12% 44 Missing and 2 partials ⚠️
router/pkg/sandbox/transpiler.go 0.00% 23 Missing ⚠️
router/pkg/sandbox/pool.go 0.00% 22 Missing ⚠️
router/pkg/mcpserver/server.go 0.00% 5 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2575       +/-   ##
===========================================
- Coverage   64.58%   32.93%   -31.66%     
===========================================
  Files         301      236       -65     
  Lines       42900    26349    -16551     
  Branches     4601        0     -4601     
===========================================
- Hits        27709     8678    -19031     
- Misses      15170    16592     +1422     
- Partials       21     1079     +1058     
Files with missing lines Coverage Δ
router/core/router_config.go 78.88% <100.00%> (ø)
router/pkg/config/config.go 10.38% <ø> (ø)
router/pkg/mcpserver/util.go 0.00% <ø> (ø)
router/pkg/sandbox/runtime.go 0.00% <0.00%> (ø)
router/pkg/mcpserver/server.go 0.00% <0.00%> (ø)
router/pkg/sandbox/pool.go 0.00% <0.00%> (ø)
router/pkg/sandbox/transpiler.go 0.00% <0.00%> (ø)
router/core/router.go 41.77% <2.12%> (ø)
router/pkg/mcpserver/execute_tsgo_validator.go 0.00% <0.00%> (ø)
router/pkg/yokoclient/client.go 0.00% <0.00%> (ø)
... and 4 more

... and 523 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

TestQJS_AsyncFunctionWithAwait is typically the first QJS test to run,
incurring the WASM module cold-start initialization cost. On GitHub
Actions runners this can exceed the default 5s timeout. Increase to 30s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
router/pkg/sandbox/runtime_qjs_test.go (1)

100-105: Consider using require for safer type assertions.

The direct type assertions on lines 102-104 will panic with opaque messages if the structure differs from expectations. Using testify's type assertion helpers or two-value form would yield clearer failure diagnostics.

💡 Optional: Safer type assertions
 	var obj map[string]any
 	require.NoError(t, json.Unmarshal(result.Value, &obj))
-	user := obj["user"].(map[string]any)
-	assert.Equal(t, "Alice", user["name"])
-	orders := obj["orders"].([]any)
+	user, ok := obj["user"].(map[string]any)
+	require.True(t, ok, "expected user to be map[string]any")
+	assert.Equal(t, "Alice", user["name"])
+	orders, ok := obj["orders"].([]any)
+	require.True(t, ok, "expected orders to be []any")
 	assert.Len(t, orders, 1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/runtime_qjs_test.go` around lines 100 - 105, The test
currently uses direct type assertions on obj["user"] and obj["orders"] which can
panic; change those to safe assertions using the two-value type assertion and
testify require helpers: e.g., retrieve obj (map[string]any) as now, then for
the user field use u, ok := obj["user"].(map[string]any) and call
require.True(t, ok, "user must be a map"), then use require.Equal(t, "Alice",
u["name"]); similarly for orders use o, ok := obj["orders"].([]any) and
require.True(t, ok, "orders must be a slice") followed by require.Len(t, o, 1).
This updates the unsafe casts around variables obj, user and orders to produce
clearer test failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@router/pkg/sandbox/runtime_qjs_test.go`:
- Around line 100-105: The test currently uses direct type assertions on
obj["user"] and obj["orders"] which can panic; change those to safe assertions
using the two-value type assertion and testify require helpers: e.g., retrieve
obj (map[string]any) as now, then for the user field use u, ok :=
obj["user"].(map[string]any) and call require.True(t, ok, "user must be a map"),
then use require.Equal(t, "Alice", u["name"]); similarly for orders use o, ok :=
obj["orders"].([]any) and require.True(t, ok, "orders must be a slice") followed
by require.Len(t, o, 1). This updates the unsafe casts around variables obj,
user and orders to produce clearer test failures.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51a943b and dc55542.

📒 Files selected for processing (1)
  • router/pkg/sandbox/runtime_qjs_test.go

Remove the dual-runtime abstraction from the sandbox package. The goja
(pure-Go) JS runtime was a fallback that lacked native Promise support
and WASM memory isolation. With QuickJS as the sole runtime, the API
is simplified: NewRuntime/NewPool/NewTranspiler no longer take a
runtime type parameter, and the stripAsyncAwait transpiler path is
removed.

Also adds missing env tags to QueryGenerationAuth config fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (3)
router/core/router.go (2)

993-999: ⚠️ Potential issue | 🟠 Major

Preserve configured CORS origins in Code Mode.

Line [996] forces AllowOrigins to *, overriding user policy and potentially widening exposure unexpectedly.

🛠️ Suggested fix
 		var corsConfig *cors.Config
 		if r.corsOptions != nil && r.corsOptions.Enabled {
 			cfg := *r.corsOptions
-			cfg.AllowOrigins = []string{"*"}
-			cfg.AllowMethods = []string{"GET", "PUT", "POST", "DELETE", "OPTIONS"}
-			cfg.AllowHeaders = append(cfg.AllowHeaders, "Content-Type", "Accept", "Authorization", "Last-Event-ID", "Mcp-Protocol-Version", "Mcp-Session-Id")
+			cfg.AllowMethods = stringsx.RemoveDuplicates(append(cfg.AllowMethods, "GET", "PUT", "POST", "DELETE", "OPTIONS"))
+			cfg.AllowHeaders = stringsx.RemoveDuplicates(append(cfg.AllowHeaders,
+				"Content-Type", "Accept", "Authorization",
+				"Last-Event-ID", "Mcp-Protocol-Version", "Mcp-Session-Id",
+			))
+			if len(cfg.AllowOrigins) == 1 && cfg.AllowOrigins[0] == "*" {
+				cfg.AllowCredentials = false
+			}
 			corsConfig = &cfg
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/core/router.go` around lines 993 - 999, The code currently forces
cfg.AllowOrigins = []string{"*"} which overrides user-specified CORS origins;
update the block that builds corsConfig (referencing r.corsOptions,
cfg.AllowOrigins, and corsConfig) so it preserves any user-provided
cfg.AllowOrigins and only sets a wildcard when no origins were configured (or
based on an explicit allow-all flag such as r.codeMode if intended), instead of
unconditionally assigning "*". Ensure other fields (AllowMethods, AllowHeaders)
remain unchanged.

1020-1033: ⚠️ Potential issue | 🟠 Major

Add bootstrap rollback for started Code Mode server.

If any later bootstrap step fails after Line [1032] (for example in ConnectRPC initialization), bootstrap returns while the Code Mode listener may remain running. Please add bootstrap-level deferred cleanup for r.codeModeServer on subsequent errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/core/router.go` around lines 1020 - 1033, After successfully creating
and starting the Code Mode server (codeModeSrv.Start()) and assigning it to
r.codeModeServer, register a bootstrap-level rollback that will Stop the server
if any subsequent bootstrap step fails (e.g., during ConnectRPC). Implement this
by adding a deferred cleanup or pushing a cleanup func into the existing
bootstrap cleanup list that calls r.codeModeServer.Stop(ctx) (or
codeModeSrv.Stop(ctx) if still in scope), checks/returns Stop error via logger
(r.logger.Warn or similar), and ensures the server is stopped before returning
an error from bootstrap; reference symbols: codeModeSrv, r.codeModeServer,
Start(), Stop(ctx), ConnectRPC.
router/pkg/sandbox/pool_test.go (1)

88-107: ⚠️ Potential issue | 🟡 Minor

Concurrency-limit assertion is currently too weak to validate pool enforcement.

trackConcurrency() returns immediately, so overlap is not guaranteed. The test can pass even if real in-flight concurrency isn’t exercised.

🧪 Suggested strengthening
 					Fn: func(args []any) (any, error) {
 						cur := concurrent.Add(1)
 						defer concurrent.Add(-1)
 						for {
 							old := maxSeen.Load()
 							if cur <= old || maxSeen.CompareAndSwap(old, cur) {
 								break
 							}
 						}
+						time.Sleep(50 * time.Millisecond) // force overlap window
 						return nil, nil
 					},
 				},
 			}, nil, nil)
@@
-	require.True(t, maxSeen.Load() <= int32(2), "max concurrent %d exceeds pool size 2", maxSeen.Load())
+	require.Equal(t, int32(2), maxSeen.Load(), "expected to saturate pool size 2, got %d", maxSeen.Load())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/pool_test.go` around lines 88 - 107, The test's
trackConcurrency() invocation returns immediately so tasks may not overlap;
update the test (in pool_test.go) to force real overlap by turning the Fn
closure used in trackConcurrency into a blocking worker: introduce a start
barrier (e.g., startCh) that each Fn waits on until all goroutines are launched,
then release the barrier so they run concurrently; keep the existing maxSeen,
concurrent, and the Fn closure logic but ensure Fn blocks on startCh before
updating maxSeen and defers decrement, then close or signal startCh after
spawning all workers and before wg.Wait(), so the require.True assertion
actually observes in-flight concurrency <= pool size.
🧹 Nitpick comments (1)
router/pkg/mcpserver/code_mode_server.go (1)

680-683: Update stale function comment in formatToolResult.

The comment mentions a toon flag, but the function has no such parameter and always attempts TOON first, then falls back to JSON/raw.

Doc-only fix
-// formatToolResult returns the sandbox result formatted for the MCP response.
-// When toon is true, the result is encoded as TOON (Token-Oriented Object Notation).
-// Falls back to JSON on encoding failure or when toon is false.
+// formatToolResult returns the sandbox result formatted for the MCP response.
+// It attempts TOON (Token-Oriented Object Notation) encoding first and falls
+// back to the raw JSON payload string on decode/encode failure.
 func (s *CodeModeServer) formatToolResult(raw json.RawMessage) string {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 680 - 683, The doc
comment for formatToolResult is stale: remove the reference to a non-existent
'toon' flag and accurately describe the current behavior of
CodeModeServer.formatToolResult — it attempts to encode the sandbox result as
TOON first, falls back to JSON on encoding failure, and returns the original raw
message if both encodings fail; update the comment text accordingly and keep it
aligned with the function signature and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@router/pkg/mcpserver/code_mode_server.go`:
- Around line 399-402: The mutation approval currently inspects the whole
document via isMutation(queryStr) and blocks even when a non-mutation operation
is selected; fix by determining the selected operation using the provided
operationName (optsMap["operationName"]) and only require approval if that
operation is a mutation: update the check around
s.config.RequireMutationApproval to parse the GraphQL document and inspect the
operation with the given operationName (or, when no operationName is supplied
and there are multiple operations, resolve the intended operation per GraphQL
rules or conservatively skip gating), e.g., add or replace isMutation(queryStr)
with a function like isSelectedOperationMutation(queryStr, operationName) and
call s.requestMutationApproval(ctx, queryStr, optsMap["variables"]) only when
that returns true.
- Around line 601-610: Replace the unbounded per-prompt goroutine loop by adding
a concurrency cap (e.g., a semaphore channel or worker pool) around the
s.yokoClient.Generate calls so at most N prompts run concurrently, and switch
the WaitGroup Add/Done pattern to the Go 1.25 style by using wg.Go(func() { ...
}) for each started worker; specifically, keep using the existing ch and
indexedResult to send results, call s.yokoClient.Generate(ctx, prompt,
schemaHash) inside the bounded worker, and ensure you close(ch) after wg.Wait()
as before.
- Around line 305-313: The search preamble references the helper
"__generate_queries" but s.generateQueriesFunc is only registered into
asyncFuncs when s.yokoClient != nil, causing runtime failures; update the search
path in code_mode_server.go to fail fast when Yoko is not configured by checking
s.yokoClient before building searchPreamble/jsCode and returning a clear error
(e.g., a tool/validation error) instead of continuing, or alternatively remove
the "__generate_queries" reference when s.yokoClient == nil; change the logic
around s.yokoClient, asyncFuncs, generateQueriesFunc, searchPreamble and jsCode
so they are consistent (either register the function when referenced or abort
with a descriptive error) to prevent the runtime failure.
- Around line 19-20: The imports in code_mode_server.go pull
github.com/mark3labs/mcp-go at v0.43.2 while this repo expects the stable
v0.36.0; either revert the module requirement to v0.36.0 or perform a
compatibility audit and explicitly bump to v0.43.2. Fix by updating the module
reference for github.com/mark3labs/mcp-go back to v0.36.0 (or, if keeping
v0.43.2, review tag diffs between v0.36.0 and v0.43.2 to confirm no breaking API
changes for symbols used by code_mode_server.go such as the mcp and server
packages and their referenced types/methods), then run go mod tidy and run tests
to ensure nothing breaks.

In `@router/pkg/sandbox/pool_test.go`:
- Around line 33-37: Replace manual wg.Add(1) + go func patterns with the
sync.WaitGroup.Go method: in the loop that currently uses for i := range 10 {
wg.Add(1); go func(idx int) { ... }(i) } capture the loop variable before
launching the goroutine (idx := i) and call wg.Go(func() { ... use idx ... })
instead; do the same for the second occurrence (the block around lines 76–80) so
both goroutine launches use wg.Go and closed-over loop variables are explicitly
captured.

In `@router/pkg/sandbox/pool.go`:
- Around line 39-49: Execute currently only checks p.closed before waiting for
p.sem which allows a race where Close runs after that check but before or during
semaphore acquisition; to fix, after successfully acquiring the semaphore in
Execute (the select case that sends to p.sem) immediately re-check
p.closed.Load(), and if it is true release the semaphore (read from p.sem) and
return ErrPoolClosed; apply the same post-acquire re-check pattern for the other
similar block around lines 60-62 so no execution proceeds after Close. Ensure
you reference the same p.sem and p.closed symbols and the Execute/Close
functions when making the changes.

In `@router/pkg/sandbox/runtime.go`:
- Around line 45-47: The Result.MarshalJSON method returns r.Value directly
which yields invalid JSON when r or r.Value is nil/empty; modify
Result.MarshalJSON to check for nil receiver or nil/zero-length Result.Value and
return the JSON null literal (i.e. []byte("null"), nil) in those cases,
otherwise return r.Value as before, ensuring Result.MarshalJSON and references
to Result.Value are updated accordingly.

---

Duplicate comments:
In `@router/core/router.go`:
- Around line 993-999: The code currently forces cfg.AllowOrigins =
[]string{"*"} which overrides user-specified CORS origins; update the block that
builds corsConfig (referencing r.corsOptions, cfg.AllowOrigins, and corsConfig)
so it preserves any user-provided cfg.AllowOrigins and only sets a wildcard when
no origins were configured (or based on an explicit allow-all flag such as
r.codeMode if intended), instead of unconditionally assigning "*". Ensure other
fields (AllowMethods, AllowHeaders) remain unchanged.
- Around line 1020-1033: After successfully creating and starting the Code Mode
server (codeModeSrv.Start()) and assigning it to r.codeModeServer, register a
bootstrap-level rollback that will Stop the server if any subsequent bootstrap
step fails (e.g., during ConnectRPC). Implement this by adding a deferred
cleanup or pushing a cleanup func into the existing bootstrap cleanup list that
calls r.codeModeServer.Stop(ctx) (or codeModeSrv.Stop(ctx) if still in scope),
checks/returns Stop error via logger (r.logger.Warn or similar), and ensures the
server is stopped before returning an error from bootstrap; reference symbols:
codeModeSrv, r.codeModeServer, Start(), Stop(ctx), ConnectRPC.

In `@router/pkg/sandbox/pool_test.go`:
- Around line 88-107: The test's trackConcurrency() invocation returns
immediately so tasks may not overlap; update the test (in pool_test.go) to force
real overlap by turning the Fn closure used in trackConcurrency into a blocking
worker: introduce a start barrier (e.g., startCh) that each Fn waits on until
all goroutines are launched, then release the barrier so they run concurrently;
keep the existing maxSeen, concurrent, and the Fn closure logic but ensure Fn
blocks on startCh before updating maxSeen and defers decrement, then close or
signal startCh after spawning all workers and before wg.Wait(), so the
require.True assertion actually observes in-flight concurrency <= pool size.

---

Nitpick comments:
In `@router/pkg/mcpserver/code_mode_server.go`:
- Around line 680-683: The doc comment for formatToolResult is stale: remove the
reference to a non-existent 'toon' flag and accurately describe the current
behavior of CodeModeServer.formatToolResult — it attempts to encode the sandbox
result as TOON first, falls back to JSON on encoding failure, and returns the
original raw message if both encodings fail; update the comment text accordingly
and keep it aligned with the function signature and behavior.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc55542 and cbd231b.

⛔ Files ignored due to path filters (1)
  • router/go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • router/core/router.go
  • router/go.mod
  • router/pkg/config/config.go
  • router/pkg/config/config.schema.json
  • router/pkg/config/testdata/config_defaults.json
  • router/pkg/config/testdata/config_full.json
  • router/pkg/mcpserver/code_mode_server.go
  • router/pkg/sandbox/pool.go
  • router/pkg/sandbox/pool_test.go
  • router/pkg/sandbox/runtime.go
  • router/pkg/sandbox/runtime_qjs_test.go
  • router/pkg/sandbox/runtime_test.go
  • router/pkg/sandbox/transpiler.go
  • router/pkg/sandbox/transpiler_test.go

Comment on lines +19 to +20
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Scanning go.mod files for mcp-go version pins..."
fd '^go\.mod$' -HI | sort | while read -r mod; do
  echo "== ${mod} =="
  rg -n 'github\.com/mark3labs/mcp-go' "$mod" || echo "  (no mcp-go dependency)"
done

Repository: wundergraph/cosmo

Length of output: 634


Address mcp-go version mismatch: router/go.mod and router-tests/go.mod use v0.43.2, not the known-stable v0.36.0.

The modules importing github.com/mark3labs/mcp-go in this PR are pinned to v0.43.2 (router/go.mod:80, router-tests/go.mod:16), not v0.36.0. Since v0.36.0 is documented as the stable version for this repo and patch releases of mcp-go lack formal release notes, review tag diffs between v0.36.0 and v0.43.2 to verify API compatibility and confirm no breaking behavior changes before keeping this version upgrade.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 19 - 20, The imports
in code_mode_server.go pull github.com/mark3labs/mcp-go at v0.43.2 while this
repo expects the stable v0.36.0; either revert the module requirement to v0.36.0
or perform a compatibility audit and explicitly bump to v0.43.2. Fix by updating
the module reference for github.com/mark3labs/mcp-go back to v0.36.0 (or, if
keeping v0.43.2, review tag diffs between v0.36.0 and v0.43.2 to confirm no
breaking API changes for symbols used by code_mode_server.go such as the mcp and
server packages and their referenced types/methods), then run go mod tidy and
run tests to ensure nothing breaks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No action needed — mcp-go version is consistent across modules.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood—the v0.43.2 version is indeed consistent across both router/go.mod and router-tests/go.mod.

Just to clarify, the original concern was about moving from the documented stable v0.36.0 (per the repo's learnings) to v0.43.2, which skips past v0.38.0 that had known regressions. If you've already verified compatibility with v0.43.2 and tested the MCP functionality, then we're all set. If you'd like, I can add a note to the knowledge base that v0.43.2 has been validated for this PR to help with future reviews.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2438
File: router-tests/go.mod:15-15
Timestamp: 2026-01-05T11:53:58.709Z
Learning: For mcp-go dependency in wundergraph/cosmo, patch releases may not have formal release notes published. Tag diffs should be reviewed to verify patch-level changes contain no API surface changes or breaking behavior before adopting new patch versions.

Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2252
File: router-tests/telemetry/telemetry_test.go:9684-9693
Timestamp: 2025-10-01T20:39:16.113Z
Learning: Repo preference: In router-tests/telemetry/telemetry_test.go, keep strict > 0 assertions for request.operation.*Time (parsingTime, normalizationTime, validationTime, planningTime) in telemetry-related tests; do not relax to >= 0 unless CI flakiness is observed.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

Comment on lines +33 to +37
for i := range 10 {
wg.Add(1)
go func(idx int) {
defer wg.Done()
cur := current.Add(1)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check Go version requirement
echo "=== Checking go.mod for Go version ==="
head -n 5 go.mod | grep '^go'

# Check the actual code at the specified lines
echo -e "\n=== Lines 33-37 in router/pkg/sandbox/pool_test.go ==="
sed -n '33,37p' router/pkg/sandbox/pool_test.go | cat -n

# Check lines 76-80 mentioned as "Also applies to"
echo -e "\n=== Lines 76-80 in router/pkg/sandbox/pool_test.go ==="
sed -n '76,80p' router/pkg/sandbox/pool_test.go | cat -n

# Broader context for both sections
echo -e "\n=== Broader context 30-45 ==="
sed -n '30,45p' router/pkg/sandbox/pool_test.go | cat -n

echo -e "\n=== Broader context 73-83 ==="
sed -n '73,83p' router/pkg/sandbox/pool_test.go | cat -n

Repository: wundergraph/cosmo

Length of output: 182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find go.mod files
echo "=== Finding go.mod files ==="
find . -name "go.mod" -type f | head -20

# Find the pool_test.go file
echo -e "\n=== Finding pool_test.go ==="
find . -name "pool_test.go" -type f

Repository: wundergraph/cosmo

Length of output: 360


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check Go version in router/go.mod
echo "=== Go version in router/go.mod ==="
head -n 5 router/go.mod

# Check the actual code at the specified lines
echo -e "\n=== Lines 33-37 in router/pkg/sandbox/pool_test.go ==="
sed -n '33,37p' router/pkg/sandbox/pool_test.go | cat -n

# Check lines 76-80 mentioned as "Also applies to"
echo -e "\n=== Lines 76-80 in router/pkg/sandbox/pool_test.go ==="
sed -n '76,80p' router/pkg/sandbox/pool_test.go | cat -n

# Broader context for both sections
echo -e "\n=== Broader context 25-45 ==="
sed -n '25,45p' router/pkg/sandbox/pool_test.go | cat -n

echo -e "\n=== Broader context 70-85 ==="
sed -n '70,85p' router/pkg/sandbox/pool_test.go | cat -n

Repository: wundergraph/cosmo

Length of output: 1661


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the full function body for the first goroutine (around line 33)
echo "=== Full context for first goroutine (lines 33-50) ==="
sed -n '33,50p' router/pkg/sandbox/pool_test.go | cat -n

# Get the full function body for the second goroutine (around line 76)
echo -e "\n=== Full context for second goroutine (lines 76-90) ==="
sed -n '76,90p' router/pkg/sandbox/pool_test.go | cat -n

Repository: wundergraph/cosmo

Length of output: 1201


Prefer sync.WaitGroup.Go for goroutine launches in tests.

Both locations (lines 33–37 and 76–80) use manual wg.Add(1) + go func patterns. Convert to wg.Go(func() { ... }) for consistency with Go 1.25+ conventions. At lines 33–37, explicitly capture the loop variable (idx := i) before the wg.Go() call to replace the parameter-passing pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/pool_test.go` around lines 33 - 37, Replace manual
wg.Add(1) + go func patterns with the sync.WaitGroup.Go method: in the loop that
currently uses for i := range 10 { wg.Add(1); go func(idx int) { ... }(i) }
capture the loop variable before launching the goroutine (idx := i) and call
wg.Go(func() { ... use idx ... }) instead; do the same for the second occurrence
(the block around lines 76–80) so both goroutine launches use wg.Go and
closed-over loop variables are explicitly captured.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — the tests already use modern Go patterns where applicable. The WaitGroup.Go pattern is used in new code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood! I'll respect your decision to keep the current patterns in the test file. Thanks for the clarification.


🧠 Learnings used
Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: SkArchon
Repo: wundergraph/cosmo PR: 2252
File: router-tests/telemetry/telemetry_test.go:9684-9693
Timestamp: 2025-10-01T20:39:16.113Z
Learning: Repo preference: In router-tests/telemetry/telemetry_test.go, keep strict > 0 assertions for request.operation.*Time (parsingTime, normalizationTime, validationTime, planningTime) in telemetry-related tests; do not relax to >= 0 unless CI flakiness is observed.

Comment on lines +39 to +49
if p.closed.Load() {
return nil, ErrPoolClosed
}

// Acquire semaphore slot
select {
case p.sem <- struct{}{}:
defer func() { <-p.sem }()
case <-ctx.Done():
return nil, ctx.Err()
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Close() and Execute() have a race that can admit executions after closure.

Execute checks closed only before waiting on the semaphore. If Close runs during that window, execution can still proceed, violating the documented behavior.

🔒 Suggested fix
 type Pool struct {
 	config ExecutionConfig
 	sem    chan struct{} // semaphore for concurrency control
 	closed atomic.Bool
+	done   chan struct{}
 }
@@
 	return &Pool{
 		config: config,
 		sem:    make(chan struct{}, size),
+		done:   make(chan struct{}),
 	}
 }
@@
 	// Acquire semaphore slot
 	select {
+	case <-p.done:
+		return nil, ErrPoolClosed
 	case p.sem <- struct{}{}:
 		defer func() { <-p.sem }()
 	case <-ctx.Done():
 		return nil, ctx.Err()
 	}
+	if p.closed.Load() {
+		return nil, ErrPoolClosed
+	}
@@
 func (p *Pool) Close() {
-	p.closed.Store(true)
+	if p.closed.CompareAndSwap(false, true) {
+		close(p.done)
+	}
 }

Also applies to: 60-62

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/pool.go` around lines 39 - 49, Execute currently only
checks p.closed before waiting for p.sem which allows a race where Close runs
after that check but before or during semaphore acquisition; to fix, after
successfully acquiring the semaphore in Execute (the select case that sends to
p.sem) immediately re-check p.closed.Load(), and if it is true release the
semaphore (read from p.sem) and return ErrPoolClosed; apply the same
post-acquire re-check pattern for the other similar block around lines 60-62 so
no execution proceeds after Close. Ensure you reference the same p.sem and
p.closed symbols and the Execute/Close functions when making the changes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — the race is benign. At worst, one extra execution runs during shutdown. The pool is not a security boundary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jensneuse, understood — thank you for the clarification. I'll note this for future reference.


✏️ Learnings added
Learnt from: jensneuse
Repo: wundergraph/cosmo PR: 2575
File: router/pkg/sandbox/pool.go:39-49
Timestamp: 2026-03-02T19:11:47.309Z
Learning: In router/pkg/sandbox/pool.go, the race condition between Close() and Execute() where an execution can proceed after closure is considered benign and acceptable. At worst, one extra execution runs during shutdown, and the pool is not treated as a security boundary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: StarpTech
Repo: wundergraph/cosmo PR: 2157
File: router-tests/go.mod:16-16
Timestamp: 2025-08-20T22:13:25.222Z
Learning: github.com/mark3labs/mcp-go v0.38.0 has regressions and should not be used in the wundergraph/cosmo project. v0.36.0 is the stable version that should be used across router-tests and other modules.

Learnt from: asoorm
Repo: wundergraph/cosmo PR: 2379
File: router/pkg/connectrpc/operation_registry_test.go:381-399
Timestamp: 2026-01-06T12:37:21.521Z
Learning: In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically. Avoid manual wg.Add(1) followed by go func() { defer wg.Done(); ... }() patterns. Apply this guidance across all Go files in the wundergraph/cosmo repository where concurrency is used.

Comment on lines +45 to +47
func (r *Result) MarshalJSON() ([]byte, error) {
return r.Value, nil
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd router/pkg/sandbox && head -80 runtime.go | cat -n

Repository: wundergraph/cosmo

Length of output: 1921


🏁 Script executed:

rg -A 10 "type Result struct" router/pkg/sandbox/

Repository: wundergraph/cosmo

Length of output: 608


🏁 Script executed:

cd router/pkg/sandbox && sed -n '30,70p' runtime.go | cat -n

Repository: wundergraph/cosmo

Length of output: 1046


🏁 Script executed:

cat > /tmp/test_marshal.go << 'EOF'
package main

import (
	"encoding/json"
	"fmt"
)

type Result struct {
	Value []byte
}

func (r *Result) MarshalJSON() ([]byte, error) {
	return r.Value, nil
}

func main() {
	// Test 1: nil Value
	r1 := &Result{Value: nil}
	b1, _ := json.Marshal(r1)
	fmt.Printf("nil Value marshals to: %q\n", string(b1))

	// Test 2: empty Value
	r2 := &Result{Value: []byte{}}
	b2, _ := json.Marshal(r2)
	fmt.Printf("empty Value marshals to: %q\n", string(b2))

	// Test 3: normal Value
	r3 := &Result{Value: []byte(`{"key":"value"}`)}
	b3, _ := json.Marshal(r3)
	fmt.Printf("normal Value marshals to: %q\n", string(b3))

	// Test 4: Check if result is valid JSON
	var dest interface{}
	err1 := json.Unmarshal(b1, &dest)
	fmt.Printf("nil Value result valid JSON: %v\n", err1 == nil)

	var dest2 interface{}
	err2 := json.Unmarshal(b2, &dest2)
	fmt.Printf("empty Value result valid JSON: %v\n", err2 == nil)
}
EOF
python3 << 'PYTHON'
import subprocess
result = subprocess.run(['go', 'run', '/tmp/test_marshal.go'], capture_output=True, text=True, cwd='/tmp')
print(result.stdout)
if result.stderr:
    print("STDERR:", result.stderr)
PYTHON

Repository: wundergraph/cosmo

Length of output: 235


Guard MarshalJSON against nil/empty payloads to return valid JSON.

When Result.Value is nil or empty, the current implementation returns an empty string, which is invalid JSON. Return the JSON null literal instead.

🛠️ Suggested fix
 func (r *Result) MarshalJSON() ([]byte, error) {
+	if r == nil || len(r.Value) == 0 {
+		return []byte("null"), nil
+	}
 	return r.Value, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/sandbox/runtime.go` around lines 45 - 47, The Result.MarshalJSON
method returns r.Value directly which yields invalid JSON when r or r.Value is
nil/empty; modify Result.MarshalJSON to check for nil receiver or
nil/zero-length Result.Value and return the JSON null literal (i.e.
[]byte("null"), nil) in those cases, otherwise return r.Value as before,
ensuring Result.MarshalJSON and references to Result.Value are updated
accordingly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't fix — MarshalJSON handles the relevant cases correctly for current usage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rate Limit Exceeded

@jensneuse have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 17 seconds before sending another message.

- CORS: preserve user-configured origins instead of overriding to wildcard
- Bootstrap: add deferred cleanup to stop Code Mode server on later failures
- Search: only include generateQueries preamble when yokoClient is configured
- Mutations: isMutation now respects operationName for multi-operation documents
- Shutdown: return errors from Stop() instead of swallowing them

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
router/pkg/mcpserver/code_mode_server.go (3)

229-240: Missing tool annotation for the execute tool.

The search tool includes a WithToolAnnotation specifying hints like ReadOnlyHint, but the execute tool omits this annotation. Since execute can perform mutations, it should include an annotation with appropriate hints (e.g., DestructiveHint: true, IdempotentHint: false).

Proposed fix
 	// Execute tool
 	s.mcpServer.AddTool(
 		mcp.NewTool(executeToolName,
 			mcp.WithDescription(executeToolDescription),
 			mcp.WithString("code",
 				mcp.Required(),
 				mcp.Description("An async arrow function. Example: async () => { const { data } = await graphql({ query: '{ users { id } }' }); return data; }"),
 			),
+			mcp.WithToolAnnotation(mcp.ToolAnnotation{
+				Title:           "Execute GraphQL Operations",
+				DestructiveHint: mcp.ToBoolPtr(true),
+				IdempotentHint:  mcp.ToBoolPtr(false),
+				OpenWorldHint:   mcp.ToBoolPtr(true),
+			}),
 		),
 		s.handleExecute(),
 	)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 229 - 240, The execute
tool registration (mcp.NewTool with executeToolName and s.handleExecute) is
missing a tool annotation indicating its destructive nature; update the tool
builder to include mcp.WithToolAnnotation(...) for the execute tool and set the
appropriate hints (e.g., DestructiveHint: true, IdempotentHint: false) so
callers understand it may perform mutations—add this annotation alongside
mcp.WithDescription and mcp.WithString in the mcp.NewTool call that constructs
the execute tool.

665-678: Consider LRU eviction instead of clearing the entire store.

When the query store reaches maxQueryStoreSize, the entire store is cleared. This means valid hashes that clients are actively using could suddenly become invalid. An LRU eviction strategy would be more graceful, removing only the least recently used entries.

For initial implementation, the clear-all strategy is acceptable, but consider tracking this as a future improvement if hash resolution failures become a problem in production. The current approach is simple and predictable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 665 - 678,
storeQueryHash currently clears the entire s.queryStore when len(s.queryStore)
>= maxQueryStoreSize which invalidates all active hashes; replace the clear-all
behavior with an LRU eviction: add an LRU structure (e.g., container/list + map
or use an lru package) tracked on the CodeModeServer, protect it with
s.queryStoreMu, and in storeQueryHash insert the new hash and evict only the
least-recently-used entry when capacity is exceeded (update access order on
lookups elsewhere when resolving hashes). Ensure you update all places that read
s.queryStore to touch the LRU so recency is tracked, keeping names like
storeQueryHash, s.queryStore, s.queryStoreMu and maxQueryStoreSize to locate the
code.

609-618: Consider using sync.WaitGroup.Go() pattern per repo conventions.

Per repo conventions for Go 1.25+, prefer using wg.Go(func() { ... }) over the manual wg.Add(1) + go func() { defer wg.Done(); ... }() pattern.

Proposed refactor
-		for i, p := range prompts {
-			wg.Add(1)
-			go func(idx int, prompt string) {
-				defer wg.Done()
-				results, err := s.yokoClient.Generate(ctx, prompt, schemaHash)
-				ch <- indexedResult{index: idx, results: results, err: err}
-			}(i, p)
-		}
+		for i, p := range prompts {
+			idx, prompt := i, p
+			wg.Go(func() {
+				results, err := s.yokoClient.Generate(ctx, prompt, schemaHash)
+				ch <- indexedResult{index: idx, results: results, err: err}
+			})
+		}

Based on learnings: "In Go code (Go 1.25+), prefer using sync.WaitGroup.Go(func()) to run a function in a new goroutine, letting the WaitGroup manage Add/Done automatically."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/pkg/mcpserver/code_mode_server.go` around lines 609 - 618, Replace the
manual wg.Add(1)+go+defer wg.Done() pattern in the loop with the Go 1.25+
sync.WaitGroup.Go usage: create local copies of loop variables (e.g., idx,
prompt := i, p) to avoid closure capture, then call wg.Go(func() { results, err
:= s.yokoClient.Generate(ctx, prompt, schemaHash); ch <- indexedResult{index:
idx, results: results, err: err} }); keep the separate goroutine that waits and
closes ch (go func() { wg.Wait(); close(ch) }()). This updates the loop using
the wg variable and preserves behavior of s.yokoClient.Generate and sending
indexedResult to ch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@router/pkg/mcpserver/code_mode_server.go`:
- Around line 229-240: The execute tool registration (mcp.NewTool with
executeToolName and s.handleExecute) is missing a tool annotation indicating its
destructive nature; update the tool builder to include
mcp.WithToolAnnotation(...) for the execute tool and set the appropriate hints
(e.g., DestructiveHint: true, IdempotentHint: false) so callers understand it
may perform mutations—add this annotation alongside mcp.WithDescription and
mcp.WithString in the mcp.NewTool call that constructs the execute tool.
- Around line 665-678: storeQueryHash currently clears the entire s.queryStore
when len(s.queryStore) >= maxQueryStoreSize which invalidates all active hashes;
replace the clear-all behavior with an LRU eviction: add an LRU structure (e.g.,
container/list + map or use an lru package) tracked on the CodeModeServer,
protect it with s.queryStoreMu, and in storeQueryHash insert the new hash and
evict only the least-recently-used entry when capacity is exceeded (update
access order on lookups elsewhere when resolving hashes). Ensure you update all
places that read s.queryStore to touch the LRU so recency is tracked, keeping
names like storeQueryHash, s.queryStore, s.queryStoreMu and maxQueryStoreSize to
locate the code.
- Around line 609-618: Replace the manual wg.Add(1)+go+defer wg.Done() pattern
in the loop with the Go 1.25+ sync.WaitGroup.Go usage: create local copies of
loop variables (e.g., idx, prompt := i, p) to avoid closure capture, then call
wg.Go(func() { results, err := s.yokoClient.Generate(ctx, prompt, schemaHash);
ch <- indexedResult{index: idx, results: results, err: err} }); keep the
separate goroutine that waits and closes ch (go func() { wg.Wait(); close(ch)
}()). This updates the loop using the wg variable and preserves behavior of
s.yokoClient.Generate and sending indexedResult to ch.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbd231b and 86d0c28.

📒 Files selected for processing (4)
  • router/core/router.go
  • router/pkg/mcpserver/code_mode_server.go
  • router/pkg/mcpserver/code_mode_server_test.go
  • router/pkg/mcpserver/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/core/router.go

jensneuse and others added 4 commits March 2, 2026 20:32
… cold start

The WASM module cold start on GitHub Actions runners can exceed 5s.
Move the 30s timeout into defaultConfig() so all sandbox tests
(including TestPool_BasicExecution) handle CI cold starts consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The goja runtime was removed — only QuickJS remains. The config schema
no longer allows runtime_type, causing the demo to fail on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
graphql("{ users { id } }") now works as shorthand for
graphql({ query: "{ users { id } }" }). This is the most intuitive API
for agents and avoids a common first-call error observed in real usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jensneuse and others added 8 commits March 3, 2026 01:22
…mprove descriptions

Replace JS sandbox-based search with direct prompt array input. Search now
takes natural language prompts and returns operations with ready-to-use JS
snippets. Improve execute_graphql descriptions based on observed model
behavior: front-load single-call constraint, add anti-pattern guidance, and
instruct compact code generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove dead code (httpServer field, GraphName config, SetHTTPClient),
use idiomatic Go patterns (errors.New, %v for errors, http.MethodPost,
Go 1.22+ loop var scoping), add maxPrompts=20 limit, and log query
store eviction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TestExecute_SimpleReturn failed in CI with 9.78s exceeding the 5s
timeout during WASM module initialization. Increase all mcpserver
sandbox test timeouts to 30s, matching the convention in
runtime_test.go.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep dlclark/regexp2 (qjs sandbox dep), take main's docker/cli v29.2.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace query store map with ristretto LRU cache for proper eviction
- Add minimum:1 constraints to sandbox config limits in JSON schema
- Add conditional auth validation (static requires token, cosmo requires credentials)
- Mark execute tool as destructive with IdempotentHint: false
- Fix hasRequiredArgs to respect GraphQL arg default values
- Handle JSON decode errors in test mock servers
- Log OTel metric initialization failures
- Use WaitGroup.Go() instead of manual Add/Done pattern
- Update RFC: remove stale goja references, fix implementation filenames
- Improve cache miss error message to guide model to re-generate queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add semaphore (cap 64) to limit concurrent async goroutines in qjs sandbox,
  preventing unbounded fan-out from user code calling async functions in loops
- Add maxItems: 20 constraint to mock OpenAPI queries array
- Add language tags to RFC markdown code fences (http, text)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dpoint-rfc

# Conflicts:
#	demo/pkg/subgraphs/employees/subgraph/schema.resolvers.go
@github-actions
Copy link
Copy Markdown

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions bot added the Stale label Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant